Shop OBEX P1 Docs P2 Docs Learn Events
Combining Mike G's webserver objects and Beau Schwabes' SNTP Demo — Parallax Forums

Combining Mike G's webserver objects and Beau Schwabes' SNTP Demo

RforbesRforbes Posts: 281
edited 2013-11-21 12:11 in Accessories
Hi all!

Earlier today, I found myself getting pretty overwhelmed trying to learn about the spinneret and the various objects that folks have written for it.

It brought a question to mind, and I just wanted to get some feedback and ideas from you all if you don't mind.

Here we have
DEMO: Using SNTP to sync the Spinneret's on-board RTC
This demo uses objects by Beau Scwabe, Timothy D. Swieter, Roy ELtham and performs an extremely useful task.

And here we have
http://spinneret.servebeer.com:5000/index.htm
This is Mike G's webserver tutorial and objects which are also extremely useful.

Here's the thing....

I'm still fairly new to Spin, and haven't even attempted to start tackling Pasm yet. I can only imagine I'm not the only one. My skill level with all things "Propeller" is equivalent to that of an EIT on day one: I can follow along (with lots of questions) and understand some basics and a little beyond, but I don't have what it takes to sign off on the spaceship bluepring (yet!) Sure their are more folks in similar situations here.

So, my question is pretty straight forward (although the answer may not be.)

Is there an object already written that uses Mike G's webserver objects AND integrates the same functionality (or perhaps the same object) that Beau's does with the SNTP rtc synchronization? If so, where can I find it? I've not had any luck in the OBEX.

If not, would you mind giving some input/advice/ideas on how to accomplish this? Some of you folks are real inspirations, and it amazes me how quickly I see you answer questions, making it seem almost effortless.

It seems each of these collections of objects are the standard de-facto and I know I don't have the ability to strip them apart and rewire them to work with one another. For all I know, it might only take 10 minutes to do or could take a year to make it happen with a skilled team. So, that's why I'm asking.... I don't want to start spending my effort in futility on this particular idea.

Don't get me wrong here- I'm all for doing the work if it's feasible, and have really enjoyed this learning experience... but sort of like the OBEX, I believe the purpose of the forum (and each of our brains) has a similar function- to make things easier by not re-inventing the wheel. Or more importantly, not inventing a square one. :)

Any guidance or thoughts you can offer would be GREATLY appreciated!

Thanks in advance,
Robert

Comments

  • TumblerTumbler Posts: 323
    edited 2012-10-27 21:39
    Robert,

    if you take a look at the changelog (httpserver file) you can see this;


    Change Log:






    Added Beau's SNTP code - 08/06/2011






    }


    Just download the files at the google code pages.
  • RforbesRforbes Posts: 281
    edited 2012-10-28 04:59
    Tumbler,

    Uhhh... ok. So there we have it- I guess I need to figure out the google thing. I must be looking at earlier revisions of these files. See what I mean? You guys have ALL the answers!! Haha! (Slinking off to take an anti-dummy pill)
  • Mike GMike G Posts: 2,702
    edited 2012-10-28 07:22
    Here's how to add Beau Schwabes' SNTP Demo to HttpServer. This is not a comprehensive example it simply shows how to call a time server and display the results.

    First, add the CON block items
          'USA Standard Time Zone Abbreviations
      #-10, HST,AtST,_PST,MST,CST,EST,AlST
      # -1, GMT {Day light saving time, we don't have dyalight savings time in AZ}
    
                  
        'USA Daylight Time Zone Abbreviations     <-  No longer used in v1.1
      '#-9, HDT,AtDT,PDT,MDT,CDT,EDT,AlDT
    
      Zone = GMT  '<- Eastern Standard Time =  GMT-5
    
        'W5100 Interface
       #0, W5100_DATA0, W5100_DATA1, W5100_DATA2, W5100_DATA3, W5100_DATA4
       #5, W5100_DATA5, W5100_DATA6, W5100_DATA7, W5100_ADDR0, W5100_ADDR1
      #10, W5100_WR, W5100_RD, W5100_CS, W5100_INT, W5100_RST, W5100_SEN
    
    
      TIME_PORT         = 123
      TIMEOUT_SECS      = 10
    
    Add these items to the VAR block
    VAR
      long  longHIGH,longLOW,MM_DD_YYYY,DW_HH_MM_SS 'Expected 4-contigous variables
    

    Add these two methods to the HttpServer
    PUB GetSntp(id, BufferAddress)| i, size, tempMask', wait
    
        'save current socket state
        tempMask := tcpMask
        SetTcpSocketMaskById(id, 0)
        
        Socket.Close(id)
        pause(delay)
        Socket.Initialize(id, UDP_PROTOCOL, uport, TIME_PORT, @sntpIp)
        pause(delay)
        
        SNTP.CreateUDPtimeheader(BufferAddress,@sntpIp)
    
        repeat 10*TIMEOUT_SECS
          socket.txUDP(id, BufferAddress) '<-- Send the UDP packet
          i := socket.rxUDP(id,BufferAddress)  
          if i == 56
             socket.Disconnect(id)  '<-- At this point we are done, we have
                                        '     the time data and don't need to keep
                                        '     the connection active.
             tcpMask := tempMask        'reset the socket
             InitializeSocket(id)
             pause(delay)
             return 1                   '<- Time Data is ready
          pause(100) '<- if 1000 = 1 sec ; 10 = 1/100th sec X 100 repeats above = 1 sec   
    
        tcpMask := tempMask        'reset the socket
        InitializeSocket(id)
        pause(delay)
        return -1                       '<- Timed out without a response
    
    PUB DisplayHumanTime
        pst.Char(13)
        pst.Char(13)
        pst.Char(13)
        pst.str(string("SNTP Server Sync time:",13))
        pst.str(string("(GMT "))
        if Zone<0
           pst.Char("-")
        else
           pst.Char("+")
        pst.str(string(" ",||Zone+48,":00) "))
        if byte[@MM_DD_YYYY][3]<10
           pst.Char("0")
        pst.dec(byte[@MM_DD_YYYY][3])
        pst.Char("/")
        if byte[@MM_DD_YYYY][2]<10
           pst.Char("0")
        pst.dec(byte[@MM_DD_YYYY][2])
        pst.Char("/")
        pst.dec(word[@MM_DD_YYYY][0])                    
        pst.Char(9)
        if byte[@DW_HH_MM_SS][2]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][2])
        pst.Char(":")
        if byte[@DW_HH_MM_SS][1]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][1])
        pst.Char(":")
        if byte[@DW_HH_MM_SS][0]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][0])
        pst.Char(13)
    

    Make a call to GetSntp by updating the main method.
    PUB Main | packetSize, id, i, reset, j, temp, alarm
      '' HTTP Service
      GetSntp(3, @tempBuff)
      SNTP.GetTransmitTimestamp(Zone,@tempBuff,@LongHIGH,@LongLOW)
      DisplayHumanTime
    

    The SNTP object formats UPD requests and decodes time server's responses.

    The GetSntp method initializes socket 3 with the remote time server's IP and port. SNTP.CreateUDPtimeheader formats the time request and socket.txUDP sends the UPD packet. socket.rxUDP places the response a in the buffer, tempBuff

    SNTP.GetTransmitTimestamp(Zone,@tempBuff,@LongHIGH,@LongLOW) decodes the received UPD packet and places the results in the VAR block items.

    Finally, DisplayHumanTime displays the time on the Parallax Serial Terminal.
  • xanatosxanatos Posts: 1,120
    edited 2013-06-26 16:31
    I'm playing with this code in HTTPServer.spin and I've added in everything posted here, and of course I have SNTP 1.2 in my OBJ block. When I check teh code with F8, it points to this line:
      GetSntp(3, @tempBuff)   
    

    and gives me the error "Expected a variable"

    Now I know I can add in tempBuff up in my dat block (yes?), but I'm not exactly sure how to specify it. How many bytes or longs, etc.... or even if that's what I should do...

    For the sake of time, here's the full code of my HTTPServer.spin at the moment...
    {{
    &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; 
    Copyright (c) 2011 AgaveRobotics LLC.
    See end of file for terms of use.
    
    File....... HTTPServer.spin 
    Author..... Mike Gebhard
    Company.... Agave Robotics LLC
    Email...... mailto:mike.gebhard@agaverobotics.com
    Started.... 11/01/2010
    Updated.... 07/16/2011
    Modified... 06/25/2013 by David Xanatos       
    &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; 
    }}
    
    {
    About:
      HTTPServer is the designed for use with the Spinneret Web server manufactured by Parallax Inc.
    
    Usage:
      HTTPServer is the top level object.
      Required objects:
            &#8226; Parallax Serial Terminal.spin
            &#8226; W5100_Indirect_Driver.spin
            &#8226; S35390A_SD-MMC_FATEngineWrapper.spin
            &#8226; Request.spin
            &#8226; Response.spin
            &#8226; StringMethods.spin
            &#8226; S35390A_RTCEngine.spin 
    
    Change Log:
     
    }
    
    
    CON
      _clkmode = xtal1 + pll16x     
      _xinfreq = 5_000_000
    
      MAX_PACKET_SIZE = $5C0 '1472   '$800 = 2048
      RxTx_BUFFER     = $800         '$600 = 1536
      TEMP_BUFFER     = $600         '$5B4 = 1460 
      TCP_PROTOCOL    = %0001        '$300 = 768 
      UDP_PROTOCOL    = %0010        '$200 = 512 
      TCP_CONNECT     = $04          '$100 = 256                 
      DELAY           = $05
      MAX_TRIES       = $05               
    
      #0, DONE, PUT, CD
    
      '' The following are added for the SNTP Object:
    
      'USA Standard Time Zone Abbreviations
      #-10, HST,AtST,_PST,MST,CST,EST,AlST
      # -1, GMT {Day light saving time, we don't have dyalight savings time in AZ}
    
                  
      'USA Daylight Time Zone Abbreviations     <-  No longer used in v1.1
      '#-9, HDT,AtDT,PDT,MDT,CDT,EDT,AlDT
    
      Zone = GMT  '<- Eastern Standard Time =  GMT-5
    
        'W5100 Interface
       #0, W5100_DATA0, W5100_DATA1, W5100_DATA2, W5100_DATA3, W5100_DATA4
       #5, W5100_DATA5, W5100_DATA6, W5100_DATA7, W5100_ADDR0, W5100_ADDR1
      #10, W5100_WR, W5100_RD, W5100_CS, W5100_INT, W5100_RST, W5100_SEN
    
    
      TIME_PORT         = 123
      TIMEOUT_SECS      = 10
                                               
    
    DAT
      mac                   byte    $00, $08, $DC, $16, $F3, $D3     
      subnet                byte    255, 255 ,255, 0
      gateway               byte    192, 168, 1, 1 
      ip                    byte    192, 168, 1, 120
      port                  word    5000
      remoteIp              byte    65, 98, 8, 151 {65.98.8.151}
      remotePort            word    80
      uport                 word    5050 
      emailIp               byte    8, 24, 153, 30    
      emailPort             word    587     ''587     ''110       ''25
      port2                 word    5010
      status                byte    $00, $00, $00, $00   
      rxdata                byte    $0[RxTx_BUFFER]
      txdata                byte    $0[RxTx_BUFFER]
      udpdata               byte    $0[TEMP_BUFFER]
      fileErrorHandle       long    $0
      debug                 byte    $0
      lastFile              byte    $0[12], 0
      closedState           byte    %0000
      openState             byte    %0000
      listenState           byte    %0000
      establishedState      byte    %0000
      closingState          byte    %0000
      closeWaitState        byte    %0000 
      lastEstblState        byte    %0000
      lastEstblStateDebug   byte    %0000
      udpListen             byte    %0000
      tcpMask               byte    %1111
      udpMask               byte    %1000   
      fifoSocketDepth       byte    $0
      fifoSocket            long    $00_00_00_00
      debugSemId            byte    $00
      debugCounter          long    $00
      stringMethods         long    $00
      closingTimeout        long    $00, $00, $00, $00
      udpLen                long    $00
      time                  byte    "00/00/0000 00:00:00", 0
      httpDate              byte    "Wed, 01 Feb 2000 01:00:00 GMT", 0
      globalCache           byte    $1
      dynamicContentPtr     long    @txdata
      tankfile              byte    "tankfarm.csv", 0
    
                  
    VAR
      long StackSpace[20]
      byte EEArray[10]                             ' For 24LC256 EEPROM data reads for sending email with data embedded. Possibly not needed now....
      long CKSetArray[7]                           ' For RTC data setting from web interface.
      long longHIGH,longLOW,MM_DD_YYYY,DW_HH_MM_SS ' Expected 4-contigous variables (This is for SNTP Object)
    
    OBJ
      pst           : "Parallax Serial Terminal"
      Socket        : "W5100_Indirect_Driver"
      SDCard        : "S35390A_SD-MMC_FATEngineWrapper"
      Request       : "Request"
      Response      : "Response"
      str           : "StringMethods"
      rtc           : "S35390A_RTCEngine"
      nums          : "Simple_Numbers_plus"    ' "Numbers"
      sntp          : "SNTP 1.2"
    
    PUB Initialize | id, size, st
    
      debug := 1    
      SDCard.Start
      stringMethods := str.Start
      Request.Constructor(stringMethods)
      Response.Constructor(stringMethods, @txdata)
    
      pst.Start(115_200)
      pause(200)
    
      'Mount the SD card
      pst.str(string("Mount SD Card - ")) 
      SDCard.mount(fileErrorHandle)
      pst.str(string("OK",13))
      
      pst.str(string("Start RTC: "))
      rtc.RTCEngineStart(29, 28, -1)
      
      pause(200)
    
      'rtc.writeTime(0, 15, 18, 0, 25, 6, 2013)  ' (second, minute, hour, day, date, month, year)
      'pause(200)
      
      pst.str(FillTime)
        
      'Start the W5100 driver
      if(Socket.Start)
        pst.str(string(13, "W5100 Driver Started", 13))
        pst.str(string(13, "Status Memory Lock ID    : "))
        pst.dec(Socket.GetLockId)
        pst.char(13) 
    
    
      if(debugSemId := locknew) == -1
        pst.str(string("Error, no HTTP server locks available", 13))
      else
        pst.str(string("HTTP Server Lock ID      : "))
        pst.dec(debugSemId)
        pst.char(13)
        
    
      'Set the Socket addresses  
      SetMac(@mac)
      SetGateway(@gateway)
      SetSubnet(@subnet)
      SetIP(@ip)
    
      ' Initailize TCP sockets (defalut setting; TCP, Port, remote ip and remote port)
      repeat id from 0 to 3
        InitializeSocket(id)
        Request.Release(id)
        pause(50)
    
      ' Set all TCP sockets to listen
      pst.char(13) 
      repeat id from 0 to 3 
        Socket.Listen(id)
        pst.str(string("TCP Socket Listener ID   : "))
        pst.dec(id)
        pst.char(13)
        pause(50)
    
      pst.Str(string(13,"Started Socket Monitoring Service", 13))
     
      cognew(StatusMonitor, @StackSpace)
      pause(250)
    
    
      pst.Str(string(13, "Initial Socket States",13))
      StackDump
    
      pst.Str(string(13, "Initial Socket Queue",13))
      QueueDump
    
      pst.str(string(13,"/////////////////////////////////////////",13))
      
      Main
    
       
    PUB Main | packetSize, id, i, reset, j, temp
      ''HTTP Service
    
      GetSntp(3, @tempBuff)
      SNTP.GetTransmitTimestamp(Zone,@tempBuff,@LongHIGH,@LongLOW)
      DisplayHumanTime
      
      repeat
    
          
         clockandemail   ' Checks the clock and sends email at 10 minute intervals.  Use this for primary
    
         
                                                   ''  Use this for a manual "force email" button?  Possibly just remove now that Spinneret is sending every 10 mins.
          dira[22] := 0  '' Sets 22 to input       ''  This block watches the buffered IO on P22 for a 0 signal, and
          if ina[22] == 0                          ''  it sends the email when there is a low pulse.  It also fires off the LED
             dira[23]~~                            ''  just because... why not.
             outa[23] := 1 ''ina[22]
             'TankLog(string("XTM-MAN,01,G.H. Berlin/Windward,ID1,Manchester NH,001,Product,06/11/2013 19:00:00,000.00,100.00"))
             pause(DELAY)
             SendTestEmail(2)
             outa[23] := 0 ''ina[22] 
      
    
     
      
        repeat until fifoSocket == 0
        
          bytefill(@rxdata, 0, RxTx_BUFFER)
    
          if(debug)
            pst.str(string(13, "----- Start of Request-----",13))
            pause(DELAY)
          else
            pause(DELAY)
            
          ' Pop the next socket handle 
          id := DequeueSocket
          if(id < 0)
            next
          
          if(debug)
            pst.str(string(13,"ID: "))
            pst.dec(id)
            pst.str(string(13, "Request Count     : "))
            pst.dec(debugCounter)
            pst.char(13)
    
          packetSize := Socket.rxTCP(id, @rxdata)
    
          reset := false
          if ((packetSize < 12) AND (strsize(@rxdata) < 12))
            repeat i from 0 to MAX_TRIES
               'pst.str(string(13,"* Retry *"))
              'Wait for a few moments and try again
              waitcnt((clkfreq/500) + cnt)
              packetSize := Socket.rxTCP(id, @rxdata)
              if(packetSize > 12)
                quit
              if(i == MAX_TRIES)
                'Clean up resource request   
                Request.Release(id)
                Socket.Disconnect(id)
                reset := true
                if(debug)
                  StackDump
                  pst.char(13)
                  QueueDump
                  pst.str(string(13,"* Timeout *",13))
                
          if(reset)
            next
    
          Request.InitializeRequest(id, @rxdata)
          
          if(debug)
            pst.char(13)
            HeaderLine1(id)
          else
            pause(DELAY)
    
          ' Process router
          Dispatcher(id)
    
          'Clean up request resource
          Request.Release(id)
    
          ' This starts the close process -> 0x00
          ' use close to force a close
          Socket.Disconnect(id)
    
          bytefill(@txdata, 0, RxTx_BUFFER)
    
          debugCounter++
    
      GotoMain
    
    PUB SendTestEmail(id) | size, tempMask, wait, stringPointer, tankNum, tankStr, Yr, Mo, Dy, Hr, Mn, Sc
        
        tempMask := tcpMask
        SetTcpSocketMaskById(id, 0)
        
        wait := 200
        
        Socket.Close(id)
        pause(delay)
        InitializeSocketForEmail(id)
        pause(delay)
        
        ' Connect to the mail server
        pst.str(string(13, "Connecting..."))
        
        Socket.Connect(id)
        pause(wait)
        repeat while !Socket.Connected(id)
        
        pst.str(string(13,"Connected... "))
        
        'Send greeting
        StringSend(id, string("EHLO xanatos@xanatos.com", 13, 10))
        pause(wait)
        
    '==============================================================================
        StringSend(id, string("AUTH LOGIN", 13,10))
        pause(wait)
    
    '   'Send user name
        StringSend(id, string("eGFuYXRvc0B4YW5hdG9zLmNvbQ==", 13, 10))   
    '
    '
    '   'Send password
        StringSend(id, string("cGVhY2VhbmR0cmFucXVpbGl0eQ==", 13, 10))  
    '==============================================================================
    
        ' From Address
        StringSend(id, string("MAIL FROM: xanatos@xanatos.com", 13, 10))
        pause(wait)
        
        ' To Address
        StringSend(id, string("RCPT TO: xanatos@xanatos.com", 13, 10))
        pause(wait)
    
        'Start of the email content
        StringSend(id, string("DATA", 13, 10))
        pause(wait)
    
        'Visible From line
        StringSend(id, string("From: xanatos@xanatos.com", 13, 10))
        pause(wait)
    
        'Visible To line
        StringSend(id, string("To: xanatos@xanatos.com", 13, 10))
        pause(wait)
    
        'Subject line
        StringSend(id, string("SUBJECT: XXXXX Data", 13, 10))
        pause(wait)
    
        'Mime-Type
        StringSend(id, string("Mime-Version: 1.0", 13, 10))
        pause(wait)
    
        'Content Type
        StringSend(id, string("Content-Type: text/plain; charset=us-ascii", 13, 10, 13, 10))
        pause(wait)
    
        rtc.readTime
        
        Mo := rtc.clockMonth
        Dy := rtc.clockDate
        Yr := rtc.clockYear
        Hr := rtc.clockHour
        Mn := rtc.clockMinute
        Sc := rtc.clockSecond
        
        repeat tankNum from 1 to 32                    ' Increase to 128 for live version!
    
          StringSend(id, string("XTM-MAN,01,0,0,0,"))  ' Hardcoded junk to keep receiving end people happy with field assignments
          StringSend(id, nums.decn(tankNum, 3))        ' Dynamically generated Tank Number
          StringSend(id, string(",0,"))                ' More junk
          StringSend(id, nums.decn(Mo, 2))             ' Dynamically obtained Time and Date from RTC.
          StringSend(id, string("/"))
          StringSend(id, nums.decn(Dy, 2))
          StringSend(id, string("/"))
          StringSend(id, nums.decn(Yr, 4))
          StringSend(id, string(" "))
          StringSend(id, nums.decn(Hr, 2))
          StringSend(id, string(":"))
          StringSend(id, nums.decn(Mn, 2))
          StringSend(id, string(":"))
          StringSend(id, nums.decn(Sc, 2))
          StringSend(id, string(","))
          StringSend(id, string("000.00,"))            ' Level Data, or...
          StringSend(id, string("050.00", 13, 10))     ' Level Data.  Not sure yet which field they want live.
          pause(wait)
    
        'Quit conversation
        StringSend(id, string(".", 13, 10))
        pause(wait)
        
        StringSend(id, string("QUIT", 13, 10))
        pause(wait)
        
        pst.str(string(13,"Done",13))
        pst.str(string(13,"Conv. Log",13))
        
        'Display log
        repeat until size := Socket.rxTCP(id, @rxdata)
        pst.str(@rxdata)
        
        pst.str(string(13, "Discon/reset skt: "))
        pst.dec(id)
        pst.char(13)
        
        ' Reset the socket
        Socket.Disconnect(id)
        pause(delay)
        
        ' Reset the tcpMask 
        tcpMask := tempMask
        
        InitializeSocket(id)
        pause(delay)
        
        return
    
    
    
    '=======================================================================================
    PUB clockandemail | mSent      ' mSent 0 means mail NOT sent during this cycle.  1 means mail WAS sent during this cycle.
                
       waitcnt(clkfreq/5+cnt)         
       rtc.readtime
     
       'pst.str(string("            "))   
       'pst.dec(rtc.clockHour)
       'pst.str(string(":"))    
       'pst.dec(rtc.clockMinute)
       'pst.str(string(":"))  
       'pst.dec(rtc.clockSecond)
       'pst.str(string(" ",13 ))
     
       if rtc.clockMinute//10 == 0   ' //5, or //10, or //15, or //20, or //30
           
           if mSent == 0
               waitcnt(clkfreq/5+cnt)   'wait for stuff to stop
               SendTestEmail(2)         'INITIALIZES THE SOCKET AND sends the email
               mSent := 1
               waitcnt(clkfreq/5+cnt)   'wait for stuff to stop
       else
           mSent := 0
              
     return
    '=======================================================================================
    
    PUB GetSntp(id, BufferAddress)| i, size, tempMask', wait
    
        'save current socket state
        tempMask := tcpMask
        SetTcpSocketMaskById(id, 0)
        
        Socket.Close(id)
        pause(delay)
        Socket.Initialize(id, UDP_PROTOCOL, uport, TIME_PORT, @sntpIp)
        pause(delay)
        
        SNTP.CreateUDPtimeheader(BufferAddress,@sntpIp)
    
        repeat 10*TIMEOUT_SECS
          socket.txUDP(id, BufferAddress) '<-- Send the UDP packet
          i := socket.rxUDP(id,BufferAddress)  
          if i == 56
             socket.Disconnect(id)  '<-- At this point we are done, we have
                                        '     the time data and don't need to keep
                                        '     the connection active.
             tcpMask := tempMask        'reset the socket
             InitializeSocket(id)
             pause(delay)
             return 1                   '<- Time Data is ready
          pause(100) '<- if 1000 = 1 sec ; 10 = 1/100th sec X 100 repeats above = 1 sec   
    
        tcpMask := tempMask        'reset the socket
        InitializeSocket(id)
        pause(delay)
        return -1                       '<- Timed out without a response
    
    PUB DisplayHumanTime
        pst.Char(13)
        pst.Char(13)
        pst.Char(13)
        pst.str(string("SNTP Server Sync time:",13))
        pst.str(string("(GMT "))
        if Zone<0
           pst.Char("-")
        else
           pst.Char("+")
        pst.str(string(" ",||Zone+48,":00) "))
        if byte[@MM_DD_YYYY][3]<10
           pst.Char("0")
        pst.dec(byte[@MM_DD_YYYY][3])
        pst.Char("/")
        if byte[@MM_DD_YYYY][2]<10
           pst.Char("0")
        pst.dec(byte[@MM_DD_YYYY][2])
        pst.Char("/")
        pst.dec(word[@MM_DD_YYYY][0])                    
        pst.Char(9)
        if byte[@DW_HH_MM_SS][2]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][2])
        pst.Char(":")
        if byte[@DW_HH_MM_SS][1]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][1])
        pst.Char(":")
        if byte[@DW_HH_MM_SS][0]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][0])
        pst.Char(13)
    
    PRI GotoMain
      Main
    
    
    PRI InitializeSocketForEmail(id)
        Socket.Initialize(id, TCP_PROTOCOL, port2, emailPort, @emailIp)
        return
    
    PRI InitializeSocket2(id)
      Socket.Initialize(id, TCP_PROTOCOL, port2, remotePort, @remoteIp)
      return
    
    PRI Dispatcher(id)
        ''Do some processing before sending the response
        if(strcomp(Request.GetFileName(id), string("led.htm")) AND Request.GetDepth(id) == 1)     
            if(Led(id))       
                    return   
        'if(strcomp(Request.GetName(id), string("post")))
        if(strcomp(Request.GetName(id), string("post")) AND strcomp(Request.GetMethod(id), string("POST")))
                Post(id)
        if(strcomp(Request.GetFileName(id), string("aled.htm")) AND Request.GetDepth(id) == 1)     
            SendLedResponse(id)
                    return           
        StaticFileHandler(id)
        return
    
    PRI Post(id) | qstr, Yr, Mo, Dy, Hr, Mn, Sc
        '' Get the post value
        CKSetArray[6] :=  Request.Post(id, string("Syear"))
        CKSetArray[5] :=  Request.Post(id, string("Smonth"))
        CKSetArray[4] :=  Request.Post(id, string("Sdate"))
        CKSetArray[3] :=  Request.Post(id, string("Sday"))
        CKSetArray[2] :=  Request.Post(id, string("Shour"))
        CKSetArray[1] :=  Request.Post(id, string("Smin"))
        CKSetArray[0] :=  Request.Post(id, string("Ssec"))
    
        Sc := str.ToInteger(CKSetArray[0])
        Mn := str.ToInteger(CKSetArray[1])
        Hr := str.ToInteger(CKSetArray[2])
        Dy := str.ToInteger(CKSetArray[4])
        Mo := str.ToInteger(CKSetArray[5])
        Yr := 2000 + str.ToInteger(CKSetArray[6])
        
        rtc.writeTime(Sc, Mn, Hr, 0, Dy, Mo, Yr) 'SET THE RTC (second, minute, hour, day, date, month, year)     
    
        waitcnt(clkfreq/5+cnt)         
    '    rtc.readtime
    '    pst.dec(rtc.clockHour)
    '    pst.str(string(":"))    
    '    pst.dec(rtc.clockMinute)
    '    pst.str(string(":"))  
    '    pst.dec(rtc.clockSecond)
    '    pst.str(string(" ",13 ))
          
        return
    
    PRI SendLedResponse(id) | headerLen, qstr
            '' Get the query string value
            qstr :=  Request.Get(id, string("led"))
      
            '' Exit if there is no querystring
            if(qstr == 0)
              return
    
            
            '' Turn the LED on if the led= value is "on"  
            if (strcomp(string("on"), qstr ))
                    LedStatus(1)
            else
                    LedStatus(0)
            
       '' Build and send the header
            '' Send the value of led= on or off
            headerLen := Response.BuildHeader(Request.GetExtension(id), 200, globalCache)
            Socket.txTCP(id, @txdata, headerLen)
            Socket.txTCP(id, qstr, strsize(qstr))
    
            return
                    
    PRI Led(id) | qstr
        '' Get the query string value
        qstr :=  Request.Get(id, string("led"))
        if (strcomp(string("on"), qstr ))
            LedStatus(1)
        else
            LedStatus(0)
            pst.str(string(" - off",13,10))   
        return
        
    PRI LedStatus(state)
        dira[23]~~
        outa[23] := state
        return
            
    PRI StaticFileHandler(id) | fileSize, i, headerLen, temp, j
      ''Serve up static files from the SDCard
      
      'pst.str(string(13,"Static File Handler",13)) 
      SDCard.changeDirectory(@approot)
      pst.char(13)
      
      'Make sure the directory exists
      ifnot(ChangeDirectory(id))
        'send 404 error
        WriteError(id)
        SDCard.changeDirectory(@approot)
        return
        
      ' Make sure the file exists
      ifnot(FileExists(Request.GetFileName(id)))
        'send 404 error
        WriteError(id)
        SDCard.changeDirectory(@approot)
        return
    
      ' Open the file for reading
      SDCard.openFile(Request.GetFileName(id), "r")
      fileSize := SDCard.getFileSize
    
      'WriteResponseHeader(id)
      'BuildHeader(extension, statusCode, expirer)
      headerLen := Response.BuildHeader(Request.GetExtension(id), 200, globalCache)
      Socket.txTCP(id, @txdata, headerLen)
      
      if fileSize < MAX_PACKET_SIZE
        ' send the file in one packet
        SDCard.readFromFile(@txdata, fileSize)
        Socket.txTCP(id, @txdata, fileSize)
      else
        ' send the file in a bunch of packets 
        repeat
          SDCard.readFromFile(@txdata, MAX_PACKET_SIZE)  
          Socket.txTCP(id, @txdata, MAX_PACKET_SIZE)
          fileSize -= MAX_PACKET_SIZE
          ' once the remaining fileSize is less then the max packet size, just send that remaining bit and quit the loop
          if fileSize < MAX_PACKET_SIZE and fileSize > 0
            SDCard.readFromFile(@txdata, fileSize)
            Socket.txTCP(id, @txdata, fileSize)
            quit
       
          ' Bailout
          if(i++ > 1_000_000)
            WriteError(id)
            quit
         
      SDCard.closeFile
      SDCard.changeDirectory(@approot)
      return
    
    
      
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' SDCard Logger
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI TankLog(logToAppend)
      '' logToAppend:  Pointer to a string of text we'd like to log
      SDCard.changeDirectory(@approot) 
    
      if(FileExists(@tankfile))
        SDCard.openFile(@tankfile, "A")
      else
        SDCard.newFile(@tankfile)
    
      SDCard.writeData(logToAppend, strsize(logToAppend))
      SDCard.writeData(@crlf_crlf, 2)
    
      SDCard.closeFile
    
      return
    
    PRI WriteError(id) | headerOffset
      '' Simple 404 error
      pst.str(string(13, "Write 404 Error",13 ))
      headerOffset := Response.BuildHeader(Request.GetExtension(id), 404, false)
      Socket.txTCP(id, @txdata, headerOffset)
      return
    
      
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Write data to a buffer
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI PushDynamicContent(content)
      ' Write the content to memory
      ' and update the pointer
      bytemove(dynamicContentPtr, content, strsize(content))
      dynamicContentPtr := dynamicContentPtr + strsize(content)
      return
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' directory and file handlers
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''  
    PRI ChangeDirectory(id) | i, found
      'Handle directory structure for this Request
      if(Request.GetDepth(id) > 1)
        repeat i from 0 to Request.GetDepth(id)-2
          'Return if the directory is not found 
          ifnot(FileExists(Request.GetPathNode(id, i)))
            return false
          found := SDCard.changeDirectory(Request.GetPathNode(id, i))
      return true 
    
      
    PRI FileExists(fileToCompare) | filenamePtr
    'Start file find at the top of the list
      SDCard.startFindFile 
      'Verify that the file exists
      repeat while filenamePtr <> 0
        filenamePtr := SDCard.nextFile 
        if(str.MatchPattern(filenamePtr, fileToCompare, 0, false ) == 0 )
          return true
    
      return false
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Time Methods and Formats
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI GetTime(id) | ptr, headerOffset
      ptr := @udpdata
      FillHttpDate
      
      bytemove(ptr, string("<p>"),3)
      ptr += 3
    
      bytemove(ptr, @httpDate, strsize(@httpDate))
      ptr += strsize(@httpDate)
      
      bytemove(ptr, string("</p>"),4)
      ptr += 3  
    
      headerOffset := Response.BuildHeader(Request.GetExtension(id), 200, false)
      Socket.txTCP(id, @txdata, headerOffset)
      StringSend(id, @udpdata)
      bytefill(@udpdata, 0, TEMP_BUFFER)
      
      return
     
     
    PRI FillTime | ptr, num
     'ToString(integerToConvert, destinationPointer)
     '00/00/0000 00:00:00
      ptr := @time
      rtc.readTime
      
    
      FillTimeHelper(rtc.clockMonth, ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockDate, ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockYear, ptr)
      ptr += 5
    
      FillTimeHelper(rtc.clockHour , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockMinute , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockSecond, ptr) 
     
      return @time
    
    
    PRI FillHttpDate | ptr, num, temp
     'ToString(integerToConvert, destinationPointer)
     'Wed, 01 Feb 2000 01:00:00 GMT
      ptr := @httpDate
      rtc.readTime
    
    
      temp := rtc.getDayString
      bytemove(ptr, temp, strsize(temp))
      ptr += strsize(temp) + 2
    
      FillTimeHelper(rtc.clockDate, ptr)
      ptr += 3
    
      temp := rtc.getMonthString
      bytemove(ptr, temp, strsize(temp))
      ptr += strsize(temp) + 1
    
      FillTimeHelper(rtc.clockYear, ptr)
      ptr += 5
    
      FillTimeHelper(rtc.clockHour , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockMinute , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockSecond, ptr)
      
      return @httpDate
     
    
    PRI FillTimeHelper(number, ptr) | offset
      offset := 0
      if(number < 10)
        offset := 1
         
      str.ToString(@number, @tempNum)
      bytemove(ptr+offset, @tempNum, strsize(@tempNum))
      
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' SDCard Logger
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI AppendLog(logToAppend)
      '' logToAppend:  Pointer to a string of text we'd like to log
      SDCard.changeDirectory(@approot) 
    
      if(FileExists(@logfile))
        SDCard.openFile(@logfile, "A")
      else
        SDCard.newFile(@logfile)
    
      SDCard.writeData(string(13,10,"----- Start "),14)
      SDCard.writeData(FillTime, 19)
      SDCard.writeData(string(" -----"),6)
      SDCard.writeData(@crlf_crlf, 2)
    
      SDCard.writeData(logToAppend, strsize(logToAppend))
      SDCard.writeData(@crlf_crlf, 2)
      
      SDCard.writeData(string("----- End "),10)
      SDCard.writeData(FillTime, 19)
      SDCard.writeData(string(" -----"),6)
      SDCard.writeData(@crlf_crlf, 2)
    
      SDCard.closeFile
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Memory Management
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI Set(DestAddress, SrcAddress, Count)
      bytemove(DestAddress, SrcAddress, Count)
      bytefill(DestAddress+Count, $0, 1)
      
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Socket helpers
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    
    PRI GetTcpSocketMask(id)
      return id & tcpMask
    
      
    PRI DecodeId(value) | tmp
        if(%0001 & value)
          return 0
        if(%0010 & value)
          return 1
        if(%0100 & value)
          return 2 
        if(%1000 & value)
          return 3
      return -1
    
    
    PRI QueueSocket(id) | tmp
      if(fifoSocketDepth > 4)
        return false
    
      tmp := |< id
      
      'Unique check
      ifnot(IsUnique(tmp))
        return false
        
      tmp <<= (fifoSocketDepth++) * 8
      
      fifoSocket |= tmp
    
      return true
    
    
    PRI IsUnique(encodedId) | tmp
      tmp := encodedId & $0F
      repeat 4
        if(encodedId & fifoSocket)
          return false
        encodedId <<= 8
      return true 
        
    
    PRI DequeueSocket | tmp
      if(fifoSocketDepth == 0)
        return -2
      repeat until not lockset(debugSemId) 
      tmp := fifoSocket & $0F
      fifoSocket >>= 8  
      fifoSocketDepth--
      lockclr(debugSemId)
      return DecodeId(tmp)
    
      
    PRI ResetSocket(id)
      Socket.Disconnect(id)                                                                                                                                 
      Socket.Close(id)
      
    PRI IsolateTcpSocketById(id) | tmp
      tmp := |< id
      tcpMask &= tmp
    
    
    PRI SetTcpSocketMaskById(id, state) | tmp
    '' The tcpMask contains the socket the the StatusMonitor monitors
      tmp := |< id
      
      if(state == 1)
        tcpMask |= tmp
      else
        tmp := !tmp
        tcpMask &= tmp 
        
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' W5100 Helper methods
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI GetCommandRegisterAddress(id)
      return Socket#_S0_CR + (id * $0100)
    
    PRI GetStatusRegisterAddress(id)
      return Socket#_S0_SR + (id * $0100)
    
        
    PRI SetMac(_firstOctet)
      Socket.WriteMACaddress(true, _firstOctet)
      return 
    
    
    PRI SetGateway(_firstOctet)
      Socket.WriteGatewayAddress(true, _firstOctet)
      return 
    
    
    PRI SetSubnet(_firstOctet)
      Socket.WriteSubnetMask(true, _firstOctet)
      return 
    
    
    PRI SetIP(_firstOctet)
      Socket.WriteIPAddress(true, _firstOctet)
      return 
    
    
    
    PRI StringSend(id, _dataPtr)
      Socket.txTCP(id, _dataPtr, strsize(_dataPtr))
      return 
    
    
    PRI SendChar(id, _dataPtr)
      Socket.txTCP(id, _dataPtr, 1)
      return 
    
     
    PRI SendChars(id, _dataPtr, _length)
      Socket.txTCP(id, _dataPtr, _length)
      return 
    
    
    PRI InitializeSocket(id)
      Socket.Initialize(id, TCP_PROTOCOL, port, remotePort, @remoteIp)
      return
    
    PRI InitializeUPDSocket(id)
      Socket.Initialize(id, UDP_PROTOCOL, uport, remotePort, @remoteIp)
      return
    
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Debug/Display Methods
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI QueueDump
      '' Display socket IDs in the queue
      '' ie 00000401 -> socket Zero is next to pop off
      pst.str(string("FIFO["))
      pst.dec(fifoSocketDepth)
      pst.str(string("] "))
      pst.hex(fifoSocket, 8)
    
        
    PRI StackDump | clsd, open, lstn, estb, clwt, clng, id, ulst
      '' This method is purely for debugging
      '' It displays the status of all socket registers
      repeat until not lockset(debugSemId)
      clsd := closedState
      open := openState
      lstn := listenState
      estb := establishedState
      clwt := closeWaitState
      clng := closingState
      ulst := udpListen
      lockclr(debugSemId)
    
      pst.char(13) 
      repeat id from 3 to 0
        pst.dec(id)
        pst.str(string("-"))
        pst.hex(status[id], 2)
        pst.str(string(" "))
        pause(1)
    
      pst.str(string(13,"clsd open lstn estb clwt clng udps", 13))
      pst.bin(clsd, 4)
      pst.str(string("-"))
      pst.bin(open, 4)
      pst.str(string("-"))
      pst.bin(lstn, 4)
      pst.str(string("-"))  
      pst.bin(estb, 4)
      pst.str(string("-"))  
      pst.bin(clwt, 4)
      pst.str(string("-"))  
      pst.bin(clng, 4)
      pst.str(string("-"))  
      pst.bin(ulst, 4)
      pst.char(13)
       
    PRI HeaderLine1(id) | i
      pst.str(Request.GetMethod(id))
      pst.char($20)
    
      i := 0
      repeat Request.GetDepth(id)
        pst.char($2F)
        pst.str(Request.GetPathNode(id, i++))
        
       
    PRI Pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return
    
    
    PRI StatusMonitor | id, tmp, value
    '' StatusMonitor is the heartbeat of the project
    '' Here we monitor the state of the Wiznet 5100's 4 sockets
      repeat
    
        Socket.GetStatus32(@status[0])
    
        ' Encode status register states
        repeat until not lockset(debugSemId)
    
        closedState := openState := listenState := establishedState := {
         } closeWaitState := closingState := 0
         
        repeat id from 0 to 3
          case(status[id])
            $00: closedState               |= |< id
                 closedState               &= tcpMask  
            $13: openState                 |= |< id
                 openState                 &= tcpMask                   
            $14: listenState               |= |< id
                 listenState               &= tcpMask
            $17: establishedState          |= |< id
                 establishedState          &= tcpMask
            $18,$1A,$1B: closingState      |= |< id
                         closingState      &= tcpMask
            $1C: closeWaitState            |= |< id
                 closeWaitState            &= tcpMask
            $1D: closingState              |= |< id
                 closingState              &= tcpMask
            $22: udpListen                 |= |< id
                 udpListen                 &= udpMask 
    
        if(lastEstblState <> establishedState)
          value := establishedState
          repeat while value > 0
            tmp := DecodeId(value)
            if(tmp > -1)
              QueueSocket(tmp)
              tmp := |< tmp
              tmp := !tmp
              value &= tmp
          lastEstblState := establishedState
    
        lockclr(debugSemId)
        
        ' Initialize a closed socket 
        if(closedState > 0)
          id := DecodeId(closedState)
          if(id > -1)
            InitializeSocket(id & tcpMask)
        
        'Start a listener on an initialized/open socket   
        if(openState > 0)
          id := DecodeId(openState)
          if(id > -1)
            Socket.Listen(id & tcpMask)
    
        ' Close the socket if the status is close/wait
        ' response processor should close the socket with disconnect
        ' there could be a timeout so we have a forced close.
        ' TODO: CCheck for a port that gets stuck in a closing state
        'if(closeWaitState > 0)
          'id := DecodeId(closeWaitState)
          'if(id > -1)
            'Socket.Close(id & tcpMask)
    
    
    
        'pause(100)
    return
        
    DAT
      approot               byte    "\", 0 
      defaultpage           byte    "index.htm", 0
      logfile               byte    "log.txt", 0
      'binFile               byte    "filename.bin", 0
      FS                    byte    "/", 0
      fn                    byte    "filename=", 0
      doublequote           byte    $22, 0
      crlf                  byte    13, 10, 0
      crlf_crlf             byte    13, 10, 13, 10, 0
      uploadfile            byte    $0[12], 0
      uploadfolder          byte    "uploads", 0
      tempNum               byte    "0000",0
      multipart             byte    "Content-Type: multipart/form-data; boundary=",0
      boundary              byte    $2D, $2D
      boundary1             byte    $0[64]
      'loadme                file    "TogglePin0.binary"                 
    
    {{
    &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
    &#9474;                                                   TERMS OF USE: MIT License                                                  &#9474;                                                            
    &#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
    &#9474;Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation    &#9474; 
    &#9474;files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,    &#9474;
    &#9474;modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software&#9474;
    &#9474;is furnished to do so, subject to the following conditions:                                                                   &#9474;
    &#9474;                                                                                                                              &#9474;
    &#9474;The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.&#9474;
    &#9474;                                                                                                                              &#9474;
    &#9474;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE          &#9474;
    &#9474;WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR         &#9474;
    &#9474;COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,   &#9474;
    &#9474;ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                         &#9474;
    &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
    }}
    
    

    Thanks!

    Dave
  • Mike GMike G Posts: 2,702
    edited 2013-06-26 17:50
    tempBuff is simply a temporary buffer to hold the SNTP results. You could replace @tempBuff with @udpdata since you're probably not using udpdata for anything.
    COM
    ...
    TEMP_BUFFER     = $300
    
    DAT
    ...
      tempBuff     byte    $0[TEMP_BUFFER] 
    
  • xanatosxanatos Posts: 1,120
    edited 2013-06-26 17:57
    :lol: Thanks Mike. I did already have a CON for TEMP_BUFFER in there, but it's set to $600. It likes everything now.

    But... now it's telling me the same thing for this line:
    Socket.Initialize(id, UDP_PROTOCOL, uport, TIME_PORT, @sntpIp)
    

    It's looking for @sntpIp. Is this just another buffer, or should I have an IP address set in there somehow?

    Did I forget to copy something from the post here? I can't figure out why I should be getting so many errors here, and I'm worried I'm missing something major somewhere (other than a complete understanding of how to specify all these "@" things... :-)
  • Mike GMike G Posts: 2,702
    edited 2013-06-26 18:11
    I'm using the following SNTP IP.
    DAT
    ...
      sntpIp                byte    69, 25, 96, 13 {San Jose CA} 
    

    Here's a list from the FEDs.
    http://tf.nist.gov/tf-cgi/servers.cgi
  • xanatosxanatos Posts: 1,120
    edited 2013-06-26 18:21
    Thanks. The SNTP object apparently exceeds my total available memory. Before SNTP 1.2, I had 78 longs left. Now that SNTP 1.2 is in and the missing pieces are taken care of, when I do an F8 to check for good compile and remaining space, I get this message:

    Object exceeds runtime memory limit by 429 longs.

    Really???

    It appears to be the CON "tempBuff" that is giving the problem. When I remove all the references to the SNTP 1.2 (all the PUBS, and comment out the lines under PUB main, and comment out everything else remaining, I'm back to my original 78 longs. When I just uncomment the CON tempBuff and F8, then I get the memory exceeded message of 327 longs. Am I somehow specifying the tempBuff wrong? Too much? ???

    So I set my CON block's TEMP_BUFFER to $300 instead of $600, and left out the PUBs for SNTP, and I had most of my original space left.

    Restoring the PUBs for SNTP and F8 now shows I'm over memory by 39 longs.

    How far down can I reduce TEMP_BUFFER and still have everything run OK? If I reduce it to $200 it leaves me 99 longs with everything restored... is $200 enough/

    Here's the total code of HTTPServer.spin again. No changes have been made to SNTP 1.2.spin, so it's just as it was downloaded from the forum.
    {{
    &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; 
    Copyright (c) 2011 AgaveRobotics LLC.
    See end of file for terms of use.
    
    File....... HTTPServer.spin 
    Author..... Mike Gebhard
    Company.... Agave Robotics LLC
    Email...... mailto:mike.gebhard@agaverobotics.com
    Started.... 11/01/2010
    Updated.... 07/16/2011
    Modified... 06/25/2013 by David Xanatos       
    &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; 
    }}
    
    {
    About:
      HTTPServer is the designed for use with the Spinneret Web server manufactured by Parallax Inc.
    
    Usage:
      HTTPServer is the top level object.
      Required objects:
            &#8226; Parallax Serial Terminal.spin
            &#8226; W5100_Indirect_Driver.spin
            &#8226; S35390A_SD-MMC_FATEngineWrapper.spin
            &#8226; Request.spin
            &#8226; Response.spin
            &#8226; StringMethods.spin
            &#8226; S35390A_RTCEngine.spin 
    
    Change Log:
     
    }
    
    
    CON
      _clkmode = xtal1 + pll16x     
      _xinfreq = 5_000_000
    
      MAX_PACKET_SIZE = $5C0 '1472   '$800 = 2048
      RxTx_BUFFER     = $800         '$600 = 1536
      TEMP_BUFFER     = $600         '$5B4 = 1460 
      TCP_PROTOCOL    = %0001        '$300 = 768 
      UDP_PROTOCOL    = %0010        '$200 = 512 
      TCP_CONNECT     = $04          '$100 = 256                 
      DELAY           = $05
      MAX_TRIES       = $05
      
      #0, DONE, PUT, CD
    
      '' The following are added for the SNTP Object:
    
      'USA Standard Time Zone Abbreviations
      #-10, HST,AtST,_PST,MST,CST,EST,AlST
      # -1, GMT {Day light saving time, we don't have dyalight savings time in AZ}
    
                  
      'USA Daylight Time Zone Abbreviations     <-  No longer used in v1.1
      '#-9, HDT,AtDT,PDT,MDT,CDT,EDT,AlDT
    
      Zone = GMT  '<- Eastern Standard Time =  GMT-5
    
        'W5100 Interface
       #0, W5100_DATA0, W5100_DATA1, W5100_DATA2, W5100_DATA3, W5100_DATA4
       #5, W5100_DATA5, W5100_DATA6, W5100_DATA7, W5100_ADDR0, W5100_ADDR1
      #10, W5100_WR, W5100_RD, W5100_CS, W5100_INT, W5100_RST, W5100_SEN
    
    
      TIME_PORT         = 123
      TIMEOUT_SECS      = 10
                                               
    
    DAT
      mac                   byte    $00, $08, $DC, $16, $F3, $D3     
      subnet                byte    255, 255 ,255, 0
      gateway               byte    192, 168, 1, 1 
      ip                    byte    192, 168, 1, 120
      port                  word    5000
      remoteIp              byte    65, 98, 8, 151 {65.98.8.151}
      remotePort            word    80
      uport                 word    5050 
      emailIp               byte    8, 24, 153, 30    
      emailPort             word    587     ''587     ''110       ''25
      port2                 word    5010
      status                byte    $00, $00, $00, $00   
      rxdata                byte    $0[RxTx_BUFFER]
      txdata                byte    $0[RxTx_BUFFER]
      udpdata               byte    $0[TEMP_BUFFER]
      fileErrorHandle       long    $0
      debug                 byte    $0
      lastFile              byte    $0[12], 0
      closedState           byte    %0000
      openState             byte    %0000
      listenState           byte    %0000
      establishedState      byte    %0000
      closingState          byte    %0000
      closeWaitState        byte    %0000 
      lastEstblState        byte    %0000
      lastEstblStateDebug   byte    %0000
      udpListen             byte    %0000
      tcpMask               byte    %1111
      udpMask               byte    %1000   
      fifoSocketDepth       byte    $0
      fifoSocket            long    $00_00_00_00
      debugSemId            byte    $00
      debugCounter          long    $00
      stringMethods         long    $00
      closingTimeout        long    $00, $00, $00, $00
      udpLen                long    $00
      time                  byte    "00/00/0000 00:00:00", 0
      httpDate              byte    "Wed, 01 Feb 2000 01:00:00 GMT", 0
      globalCache           byte    $1
      dynamicContentPtr     long    @txdata
      tankfile              byte    "tankfarm.csv", 0
      tempBuff              byte    $0[TEMP_BUFFER]
      sntpIp                byte    69, 25, 96, 13 {San Jose CA}
                  
    VAR
      long StackSpace[20]
      byte EEArray[10]                             ' For 24LC256 EEPROM data reads for sending email with data embedded.
      long CKSetArray[7]                           ' For RTC data setting from web interface.
      long longHIGH,longLOW,MM_DD_YYYY,DW_HH_MM_SS ' Expected 4-contigous variables (This is for SNTP Object)
    
    OBJ
      pst           : "Parallax Serial Terminal"
      Socket        : "W5100_Indirect_Driver"
      SDCard        : "S35390A_SD-MMC_FATEngineWrapper"
      Request       : "Request"
      Response      : "Response"
      str           : "StringMethods"
      rtc           : "S35390A_RTCEngine"
      nums          : "Simple_Numbers_plus"    ' "Numbers"
      sntp          : "SNTP 1.2"
    
    PUB Initialize | id, size, st
    
      debug := 1    
      SDCard.Start
      stringMethods := str.Start
      Request.Constructor(stringMethods)
      Response.Constructor(stringMethods, @txdata)
    
      pst.Start(115_200)
      pause(200)
    
      'Mount the SD card
      pst.str(string("Mount SD Card - ")) 
      SDCard.mount(fileErrorHandle)
      pst.str(string("OK",13))
      
      pst.str(string("Start RTC: "))
      rtc.RTCEngineStart(29, 28, -1)
      
      pause(200)
    
      'rtc.writeTime(0, 15, 18, 0, 25, 6, 2013)  ' (second, minute, hour, day, date, month, year)
      'pause(200)
      
      pst.str(FillTime)
        
      'Start the W5100 driver
      if(Socket.Start)
        pst.str(string(13, "W5100 Driver Started", 13))
        pst.str(string(13, "Status Memory Lock ID    : "))
        pst.dec(Socket.GetLockId)
        pst.char(13) 
    
    
      if(debugSemId := locknew) == -1
        pst.str(string("Error, no HTTP server locks available", 13))
      else
        pst.str(string("HTTP Server Lock ID      : "))
        pst.dec(debugSemId)
        pst.char(13)
        
    
      'Set the Socket addresses  
      SetMac(@mac)
      SetGateway(@gateway)
      SetSubnet(@subnet)
      SetIP(@ip)
    
      ' Initailize TCP sockets (defalut setting; TCP, Port, remote ip and remote port)
      repeat id from 0 to 3
        InitializeSocket(id)
        Request.Release(id)
        pause(50)
    
      ' Set all TCP sockets to listen
      pst.char(13) 
      repeat id from 0 to 3 
        Socket.Listen(id)
        pst.str(string("TCP Socket Listener ID   : "))
        pst.dec(id)
        pst.char(13)
        pause(50)
    
      pst.Str(string(13,"Started Socket Monitoring Service", 13))
     
      cognew(StatusMonitor, @StackSpace)
      pause(250)
    
    
      pst.Str(string(13, "Initial Socket States",13))
      StackDump
    
      pst.Str(string(13, "Initial Socket Queue",13))
      QueueDump
    
      pst.str(string(13,"/////////////////////////////////////////",13))
      
      Main
    
       
    PUB Main | packetSize, id, i, reset, j, temp
      ''HTTP Service
    
      GetSntp(3, @tempBuff)
      SNTP.GetTransmitTimestamp(Zone,@tempBuff,@LongHIGH,@LongLOW)
      DisplayHumanTime
      
      repeat
    
          
         clockandemail   ' Checks the clock and sends email at 10 minute intervals.  Use this for primary
    
         
                                                   ''  Use this for a manual "force email" button?
          dira[22] := 0  '' Sets 22 to input       ''  This block watches the buffered IO on P22 for a 0 signal, and
          if ina[22] == 0                          ''  it sends the email when there is a low pulse.  It also fires off the LED
             dira[23]~~                            ''  just because... why not.
             outa[23] := 1 ''ina[22]
             pause(DELAY)
             SendTestEmail(2)
             outa[23] := 0 ''ina[22] 
      
    
     
      
        repeat until fifoSocket == 0
        
          bytefill(@rxdata, 0, RxTx_BUFFER)
    
          if(debug)
            pst.str(string(13, "----- Start of Request-----",13))
            pause(DELAY)
          else
            pause(DELAY)
            
          ' Pop the next socket handle 
          id := DequeueSocket
          if(id < 0)
            next
          
          if(debug)
            pst.str(string(13,"ID: "))
            pst.dec(id)
            pst.str(string(13, "Request Count     : "))
            pst.dec(debugCounter)
            pst.char(13)
    
          packetSize := Socket.rxTCP(id, @rxdata)
    
          reset := false
          if ((packetSize < 12) AND (strsize(@rxdata) < 12))
            repeat i from 0 to MAX_TRIES
               'pst.str(string(13,"* Retry *"))
              'Wait for a few moments and try again
              waitcnt((clkfreq/500) + cnt)
              packetSize := Socket.rxTCP(id, @rxdata)
              if(packetSize > 12)
                quit
              if(i == MAX_TRIES)
                'Clean up resource request   
                Request.Release(id)
                Socket.Disconnect(id)
                reset := true
                if(debug)
                  StackDump
                  pst.char(13)
                  QueueDump
                  pst.str(string(13,"* Timeout *",13))
                
          if(reset)
            next
    
          Request.InitializeRequest(id, @rxdata)
          
          if(debug)
            pst.char(13)
            HeaderLine1(id)
          else
            pause(DELAY)
    
          ' Process router
          Dispatcher(id)
    
          'Clean up request resource
          Request.Release(id)
    
          ' This starts the close process -> 0x00
          ' use close to force a close
          Socket.Disconnect(id)
    
          bytefill(@txdata, 0, RxTx_BUFFER)
    
          debugCounter++
    
      GotoMain
    
    PUB SendTestEmail(id) | size, tempMask, wait, stringPointer, tankNum, tankStr, Yr, Mo, Dy, Hr, Mn, Sc
        
        tempMask := tcpMask
        SetTcpSocketMaskById(id, 0)
        
        wait := 200
        
        Socket.Close(id)
        pause(delay)
        InitializeSocketForEmail(id)
        pause(delay)
        
        ' Connect to the mail server
        pst.str(string(13, "Connecting..."))
        
        Socket.Connect(id)
        pause(wait)
        repeat while !Socket.Connected(id)
        
        pst.str(string(13,"Connected... "))
        
        'Send greeting
        StringSend(id, string("EHLO xanatos@xanatos.com", 13, 10))
        pause(wait)
        
    '==============================================================================
        StringSend(id, string("AUTH LOGIN", 13,10))
        pause(wait)
    
    '   'Send user name
        StringSend(id, string("eGFuYXRvc0B4YW5hdG9zLmNvbQ==", 13, 10))   
    '
    '
    '   'Send password
        StringSend(id, string("cGVhY2VhbmR0cmFucXVpbGl0eQ==", 13, 10))  
    '==============================================================================
    
        ' From Address
        StringSend(id, string("MAIL FROM: xanatos@xanatos.com", 13, 10))
        pause(wait)
        
        ' To Address
        StringSend(id, string("RCPT TO: xanatos@xanatos.com", 13, 10))
        pause(wait)
    
        'Start of the email content
        StringSend(id, string("DATA", 13, 10))
        pause(wait)
    
        'Visible From line
        StringSend(id, string("From: xanatos@xanatos.com", 13, 10))
        pause(wait)
    
        'Visible To line
        StringSend(id, string("To: xanatos@xanatos.com", 13, 10))
        pause(wait)
    
        'Subject line
        StringSend(id, string("SUBJECT: XXXXX Data", 13, 10))
        pause(wait)
    
        'Mime-Type
        StringSend(id, string("Mime-Version: 1.0", 13, 10))
        pause(wait)
    
        'Content Type
        StringSend(id, string("Content-Type: text/plain; charset=us-ascii", 13, 10, 13, 10))
        pause(wait)
    
        rtc.readTime
        
        Mo := rtc.clockMonth
        Dy := rtc.clockDate
        Yr := rtc.clockYear
        Hr := rtc.clockHour
        Mn := rtc.clockMinute
        Sc := rtc.clockSecond
        
        repeat tankNum from 1 to 32                    ' Increase to 128 for live version!
    
          StringSend(id, string("XTM-MAN,01,0,0,0,"))  ' Hardcoded junk to keep receiving end happy with field assignments
          StringSend(id, nums.decn(tankNum, 3))        ' Dynamically generated Tank Number
          StringSend(id, string(",0,"))                ' More junk
          StringSend(id, nums.decn(Mo, 2))             ' Dynamically obtained Time and Date from RTC.
          StringSend(id, string("/"))
          StringSend(id, nums.decn(Dy, 2))
          StringSend(id, string("/"))
          StringSend(id, nums.decn(Yr, 4))
          StringSend(id, string(" "))
          StringSend(id, nums.decn(Hr, 2))
          StringSend(id, string(":"))
          StringSend(id, nums.decn(Mn, 2))
          StringSend(id, string(":"))
          StringSend(id, nums.decn(Sc, 2))
          StringSend(id, string(","))
          StringSend(id, string("000.00,"))            ' Level Data, or...
          StringSend(id, string("050.00", 13, 10))     ' Level Data.  Not sure yet which field they want live.
          pause(wait)
    
        'Quit conversation
        StringSend(id, string(".", 13, 10))
        pause(wait)
        
        StringSend(id, string("QUIT", 13, 10))
        pause(wait)
        
        pst.str(string(13,"Done",13))
        pst.str(string(13,"Conv. Log",13))
        
        'Display log
        repeat until size := Socket.rxTCP(id, @rxdata)
        pst.str(@rxdata)
        
        pst.str(string(13, "Discon/reset skt: "))
        pst.dec(id)
        pst.char(13)
        
        ' Reset the socket
        Socket.Disconnect(id)
        pause(delay)
        
        ' Reset the tcpMask 
        tcpMask := tempMask
        
        InitializeSocket(id)
        pause(delay)
        
        return
    
    
    
    '=======================================================================================
    PUB clockandemail | mSent      ' mSent 0 means mail NOT sent during this cycle.  1 means mail WAS sent during this cycle.
                
       waitcnt(clkfreq/5+cnt)         
       rtc.readtime
     
       'pst.str(string("            "))   
       'pst.dec(rtc.clockHour)
       'pst.str(string(":"))    
       'pst.dec(rtc.clockMinute)
       'pst.str(string(":"))  
       'pst.dec(rtc.clockSecond)
       'pst.str(string(" ",13 ))
     
       if rtc.clockMinute//10 == 0   ' //5, or //10, or //15, or //20, or //30
           
           if mSent == 0
               waitcnt(clkfreq/5+cnt)   'wait for stuff to stop
               SendTestEmail(2)         'INITIALIZES THE SOCKET AND sends the email
               mSent := 1
               waitcnt(clkfreq/5+cnt)   'wait for stuff to stop
       else
           mSent := 0
              
     return
    '=======================================================================================
    
    PUB GetSntp(id, BufferAddress)| i, size, tempMask', wait
    
        'save current socket state
        tempMask := tcpMask
        SetTcpSocketMaskById(id, 0)
        
        Socket.Close(id)
        pause(delay)
        Socket.Initialize(id, UDP_PROTOCOL, uport, TIME_PORT, @sntpIp)
        pause(delay)
        
        SNTP.CreateUDPtimeheader(BufferAddress,@sntpIp)
    
        repeat 10*TIMEOUT_SECS
          socket.txUDP(id, BufferAddress) '<-- Send the UDP packet
          i := socket.rxUDP(id,BufferAddress)  
          if i == 56
             socket.Disconnect(id)  '<-- At this point we are done, we have
                                        '     the time data and don't need to keep
                                        '     the connection active.
             tcpMask := tempMask        'reset the socket
             InitializeSocket(id)
             pause(delay)
             return 1                   '<- Time Data is ready
          pause(100) '<- if 1000 = 1 sec ; 10 = 1/100th sec X 100 repeats above = 1 sec   
    
        tcpMask := tempMask        'reset the socket
        InitializeSocket(id)
        pause(delay)
        return -1                       '<- Timed out without a response
    
    PUB DisplayHumanTime
        pst.Char(13)
        pst.Char(13)
        pst.Char(13)
        pst.str(string("SNTP Server Sync time:",13))
        pst.str(string("(GMT "))
        if Zone<0
           pst.Char("-")
        else
           pst.Char("+")
        pst.str(string(" ",||Zone+48,":00) "))
        if byte[@MM_DD_YYYY][3]<10
           pst.Char("0")
        pst.dec(byte[@MM_DD_YYYY][3])
        pst.Char("/")
        if byte[@MM_DD_YYYY][2]<10
           pst.Char("0")
        pst.dec(byte[@MM_DD_YYYY][2])
        pst.Char("/")
        pst.dec(word[@MM_DD_YYYY][0])                    
        pst.Char(9)
        if byte[@DW_HH_MM_SS][2]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][2])
        pst.Char(":")
        if byte[@DW_HH_MM_SS][1]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][1])
        pst.Char(":")
        if byte[@DW_HH_MM_SS][0]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][0])
        pst.Char(13)
    
    PRI GotoMain
      Main
    
    
    PRI InitializeSocketForEmail(id)
        Socket.Initialize(id, TCP_PROTOCOL, port2, emailPort, @emailIp)
        return
    
    PRI InitializeSocket2(id)
      Socket.Initialize(id, TCP_PROTOCOL, port2, remotePort, @remoteIp)
      return
    
    PRI Dispatcher(id)
        ''Do some processing before sending the response
        if(strcomp(Request.GetFileName(id), string("led.htm")) AND Request.GetDepth(id) == 1)     
            if(Led(id))       
                    return   
        'if(strcomp(Request.GetName(id), string("post")))
        if(strcomp(Request.GetName(id), string("post")) AND strcomp(Request.GetMethod(id), string("POST")))
                Post(id)
        if(strcomp(Request.GetFileName(id), string("aled.htm")) AND Request.GetDepth(id) == 1)     
            SendLedResponse(id)
                    return           
        StaticFileHandler(id)
        return
    
    PRI Post(id) | qstr, Yr, Mo, Dy, Hr, Mn, Sc
        '' Get the post value
        CKSetArray[6] :=  Request.Post(id, string("Syear"))
        CKSetArray[5] :=  Request.Post(id, string("Smonth"))
        CKSetArray[4] :=  Request.Post(id, string("Sdate"))
        CKSetArray[3] :=  Request.Post(id, string("Sday"))
        CKSetArray[2] :=  Request.Post(id, string("Shour"))
        CKSetArray[1] :=  Request.Post(id, string("Smin"))
        CKSetArray[0] :=  Request.Post(id, string("Ssec"))
    
        Sc := str.ToInteger(CKSetArray[0])
        Mn := str.ToInteger(CKSetArray[1])
        Hr := str.ToInteger(CKSetArray[2])
        Dy := str.ToInteger(CKSetArray[4])
        Mo := str.ToInteger(CKSetArray[5])
        Yr := 2000 + str.ToInteger(CKSetArray[6])
        
        rtc.writeTime(Sc, Mn, Hr, 0, Dy, Mo, Yr) 'SET THE RTC (second, minute, hour, day, date, month, year)     
    
        waitcnt(clkfreq/5+cnt)         
    '    rtc.readtime
    '    pst.dec(rtc.clockHour)
    '    pst.str(string(":"))    
    '    pst.dec(rtc.clockMinute)
    '    pst.str(string(":"))  
    '    pst.dec(rtc.clockSecond)
    '    pst.str(string(" ",13 ))
          
        return
    
    PRI SendLedResponse(id) | headerLen, qstr
            '' Get the query string value
            qstr :=  Request.Get(id, string("led"))
      
            '' Exit if there is no querystring
            if(qstr == 0)
              return
    
            
            '' Turn the LED on if the led= value is "on"  
            if (strcomp(string("on"), qstr ))
                    LedStatus(1)
            else
                    LedStatus(0)
            
       '' Build and send the header
            '' Send the value of led= on or off
            headerLen := Response.BuildHeader(Request.GetExtension(id), 200, globalCache)
            Socket.txTCP(id, @txdata, headerLen)
            Socket.txTCP(id, qstr, strsize(qstr))
    
            return
                    
    PRI Led(id) | qstr
        '' Get the query string value
        qstr :=  Request.Get(id, string("led"))
        if (strcomp(string("on"), qstr ))
            LedStatus(1)
        else
            LedStatus(0)
            pst.str(string(" - off",13,10))   
        return
        
    PRI LedStatus(state)
        dira[23]~~
        outa[23] := state
        return
            
    PRI StaticFileHandler(id) | fileSize, i, headerLen, temp, j
      ''Serve up static files from the SDCard
      
      'pst.str(string(13,"Static File Handler",13)) 
      SDCard.changeDirectory(@approot)
      pst.char(13)
      
      'Make sure the directory exists
      ifnot(ChangeDirectory(id))
        'send 404 error
        WriteError(id)
        SDCard.changeDirectory(@approot)
        return
        
      ' Make sure the file exists
      ifnot(FileExists(Request.GetFileName(id)))
        'send 404 error
        WriteError(id)
        SDCard.changeDirectory(@approot)
        return
    
      ' Open the file for reading
      SDCard.openFile(Request.GetFileName(id), "r")
      fileSize := SDCard.getFileSize
    
      'WriteResponseHeader(id)
      'BuildHeader(extension, statusCode, expirer)
      headerLen := Response.BuildHeader(Request.GetExtension(id), 200, globalCache)
      Socket.txTCP(id, @txdata, headerLen)
      
      if fileSize < MAX_PACKET_SIZE
        ' send the file in one packet
        SDCard.readFromFile(@txdata, fileSize)
        Socket.txTCP(id, @txdata, fileSize)
      else
        ' send the file in a bunch of packets 
        repeat
          SDCard.readFromFile(@txdata, MAX_PACKET_SIZE)  
          Socket.txTCP(id, @txdata, MAX_PACKET_SIZE)
          fileSize -= MAX_PACKET_SIZE
          ' once the remaining fileSize is less then the max packet size, just send that remaining bit and quit the loop
          if fileSize < MAX_PACKET_SIZE and fileSize > 0
            SDCard.readFromFile(@txdata, fileSize)
            Socket.txTCP(id, @txdata, fileSize)
            quit
       
          ' Bailout
          if(i++ > 1_000_000)
            WriteError(id)
            quit
         
      SDCard.closeFile
      SDCard.changeDirectory(@approot)
      return
    
    
      
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' SDCard Logger
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI TankLog(logToAppend)
      '' logToAppend:  Pointer to a string of text we'd like to log
      SDCard.changeDirectory(@approot) 
    
      if(FileExists(@tankfile))
        SDCard.openFile(@tankfile, "A")
      else
        SDCard.newFile(@tankfile)
    
      SDCard.writeData(logToAppend, strsize(logToAppend))
      SDCard.writeData(@crlf_crlf, 2)
    
      SDCard.closeFile
    
      return
    
    PRI WriteError(id) | headerOffset
      '' Simple 404 error
      pst.str(string(13, "Write 404 Error",13 ))
      headerOffset := Response.BuildHeader(Request.GetExtension(id), 404, false)
      Socket.txTCP(id, @txdata, headerOffset)
      return
    
      
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Write data to a buffer
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI PushDynamicContent(content)
      ' Write the content to memory
      ' and update the pointer
      bytemove(dynamicContentPtr, content, strsize(content))
      dynamicContentPtr := dynamicContentPtr + strsize(content)
      return
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' directory and file handlers
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''  
    PRI ChangeDirectory(id) | i, found
      'Handle directory structure for this Request
      if(Request.GetDepth(id) > 1)
        repeat i from 0 to Request.GetDepth(id)-2
          'Return if the directory is not found 
          ifnot(FileExists(Request.GetPathNode(id, i)))
            return false
          found := SDCard.changeDirectory(Request.GetPathNode(id, i))
      return true 
    
      
    PRI FileExists(fileToCompare) | filenamePtr
    'Start file find at the top of the list
      SDCard.startFindFile 
      'Verify that the file exists
      repeat while filenamePtr <> 0
        filenamePtr := SDCard.nextFile 
        if(str.MatchPattern(filenamePtr, fileToCompare, 0, false ) == 0 )
          return true
    
      return false
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Time Methods and Formats
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI GetTime(id) | ptr, headerOffset
      ptr := @udpdata
      FillHttpDate
      
      bytemove(ptr, string("<p>"),3)
      ptr += 3
    
      bytemove(ptr, @httpDate, strsize(@httpDate))
      ptr += strsize(@httpDate)
      
      bytemove(ptr, string("</p>"),4)
      ptr += 3  
    
      headerOffset := Response.BuildHeader(Request.GetExtension(id), 200, false)
      Socket.txTCP(id, @txdata, headerOffset)
      StringSend(id, @udpdata)
      bytefill(@udpdata, 0, TEMP_BUFFER)
      
      return
     
     
    PRI FillTime | ptr, num
     'ToString(integerToConvert, destinationPointer)
     '00/00/0000 00:00:00
      ptr := @time
      rtc.readTime
      
    
      FillTimeHelper(rtc.clockMonth, ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockDate, ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockYear, ptr)
      ptr += 5
    
      FillTimeHelper(rtc.clockHour , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockMinute , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockSecond, ptr) 
     
      return @time
    
    
    PRI FillHttpDate | ptr, num, temp
     'ToString(integerToConvert, destinationPointer)
     'Wed, 01 Feb 2000 01:00:00 GMT
      ptr := @httpDate
      rtc.readTime
    
    
      temp := rtc.getDayString
      bytemove(ptr, temp, strsize(temp))
      ptr += strsize(temp) + 2
    
      FillTimeHelper(rtc.clockDate, ptr)
      ptr += 3
    
      temp := rtc.getMonthString
      bytemove(ptr, temp, strsize(temp))
      ptr += strsize(temp) + 1
    
      FillTimeHelper(rtc.clockYear, ptr)
      ptr += 5
    
      FillTimeHelper(rtc.clockHour , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockMinute , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockSecond, ptr)
      
      return @httpDate
     
    
    PRI FillTimeHelper(number, ptr) | offset
      offset := 0
      if(number < 10)
        offset := 1
         
      str.ToString(@number, @tempNum)
      bytemove(ptr+offset, @tempNum, strsize(@tempNum))
      
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' SDCard Logger
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI AppendLog(logToAppend)
      '' logToAppend:  Pointer to a string of text we'd like to log
      SDCard.changeDirectory(@approot) 
    
      if(FileExists(@logfile))
        SDCard.openFile(@logfile, "A")
      else
        SDCard.newFile(@logfile)
    
      SDCard.writeData(string(13,10,"----- Start "),14)
      SDCard.writeData(FillTime, 19)
      SDCard.writeData(string(" -----"),6)
      SDCard.writeData(@crlf_crlf, 2)
    
      SDCard.writeData(logToAppend, strsize(logToAppend))
      SDCard.writeData(@crlf_crlf, 2)
      
      SDCard.writeData(string("----- End "),10)
      SDCard.writeData(FillTime, 19)
      SDCard.writeData(string(" -----"),6)
      SDCard.writeData(@crlf_crlf, 2)
    
      SDCard.closeFile
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Memory Management
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI Set(DestAddress, SrcAddress, Count)
      bytemove(DestAddress, SrcAddress, Count)
      bytefill(DestAddress+Count, $0, 1)
      
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Socket helpers
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    
    PRI GetTcpSocketMask(id)
      return id & tcpMask
    
      
    PRI DecodeId(value) | tmp
        if(%0001 & value)
          return 0
        if(%0010 & value)
          return 1
        if(%0100 & value)
          return 2 
        if(%1000 & value)
          return 3
      return -1
    
    
    PRI QueueSocket(id) | tmp
      if(fifoSocketDepth > 4)
        return false
    
      tmp := |< id
      
      'Unique check
      ifnot(IsUnique(tmp))
        return false
        
      tmp <<= (fifoSocketDepth++) * 8
      
      fifoSocket |= tmp
    
      return true
    
    
    PRI IsUnique(encodedId) | tmp
      tmp := encodedId & $0F
      repeat 4
        if(encodedId & fifoSocket)
          return false
        encodedId <<= 8
      return true 
        
    
    PRI DequeueSocket | tmp
      if(fifoSocketDepth == 0)
        return -2
      repeat until not lockset(debugSemId) 
      tmp := fifoSocket & $0F
      fifoSocket >>= 8  
      fifoSocketDepth--
      lockclr(debugSemId)
      return DecodeId(tmp)
    
      
    PRI ResetSocket(id)
      Socket.Disconnect(id)                                                                                                                                 
      Socket.Close(id)
      
    PRI IsolateTcpSocketById(id) | tmp
      tmp := |< id
      tcpMask &= tmp
    
    
    PRI SetTcpSocketMaskById(id, state) | tmp
    '' The tcpMask contains the socket the the StatusMonitor monitors
      tmp := |< id
      
      if(state == 1)
        tcpMask |= tmp
      else
        tmp := !tmp
        tcpMask &= tmp 
        
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' W5100 Helper methods
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI GetCommandRegisterAddress(id)
      return Socket#_S0_CR + (id * $0100)
    
    PRI GetStatusRegisterAddress(id)
      return Socket#_S0_SR + (id * $0100)
    
        
    PRI SetMac(_firstOctet)
      Socket.WriteMACaddress(true, _firstOctet)
      return 
    
    
    PRI SetGateway(_firstOctet)
      Socket.WriteGatewayAddress(true, _firstOctet)
      return 
    
    
    PRI SetSubnet(_firstOctet)
      Socket.WriteSubnetMask(true, _firstOctet)
      return 
    
    
    PRI SetIP(_firstOctet)
      Socket.WriteIPAddress(true, _firstOctet)
      return 
    
    
    
    PRI StringSend(id, _dataPtr)
      Socket.txTCP(id, _dataPtr, strsize(_dataPtr))
      return 
    
    
    PRI SendChar(id, _dataPtr)
      Socket.txTCP(id, _dataPtr, 1)
      return 
    
     
    PRI SendChars(id, _dataPtr, _length)
      Socket.txTCP(id, _dataPtr, _length)
      return 
    
    
    PRI InitializeSocket(id)
      Socket.Initialize(id, TCP_PROTOCOL, port, remotePort, @remoteIp)
      return
    
    PRI InitializeUPDSocket(id)
      Socket.Initialize(id, UDP_PROTOCOL, uport, remotePort, @remoteIp)
      return
    
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Debug/Display Methods
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI QueueDump
      '' Display socket IDs in the queue
      '' ie 00000401 -> socket Zero is next to pop off
      pst.str(string("FIFO["))
      pst.dec(fifoSocketDepth)
      pst.str(string("] "))
      pst.hex(fifoSocket, 8)
    
        
    PRI StackDump | clsd, open, lstn, estb, clwt, clng, id, ulst
      '' This method is purely for debugging
      '' It displays the status of all socket registers
      repeat until not lockset(debugSemId)
      clsd := closedState
      open := openState
      lstn := listenState
      estb := establishedState
      clwt := closeWaitState
      clng := closingState
      ulst := udpListen
      lockclr(debugSemId)
    
      pst.char(13) 
      repeat id from 3 to 0
        pst.dec(id)
        pst.str(string("-"))
        pst.hex(status[id], 2)
        pst.str(string(" "))
        pause(1)
    
      pst.str(string(13,"clsd open lstn estb clwt clng udps", 13))
      pst.bin(clsd, 4)
      pst.str(string("-"))
      pst.bin(open, 4)
      pst.str(string("-"))
      pst.bin(lstn, 4)
      pst.str(string("-"))  
      pst.bin(estb, 4)
      pst.str(string("-"))  
      pst.bin(clwt, 4)
      pst.str(string("-"))  
      pst.bin(clng, 4)
      pst.str(string("-"))  
      pst.bin(ulst, 4)
      pst.char(13)
       
    PRI HeaderLine1(id) | i
      pst.str(Request.GetMethod(id))
      pst.char($20)
    
      i := 0
      repeat Request.GetDepth(id)
        pst.char($2F)
        pst.str(Request.GetPathNode(id, i++))
        
       
    PRI Pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return
    
    
    PRI StatusMonitor | id, tmp, value
    '' StatusMonitor is the heartbeat of the project
    '' Here we monitor the state of the Wiznet 5100's 4 sockets
      repeat
    
        Socket.GetStatus32(@status[0])
    
        ' Encode status register states
        repeat until not lockset(debugSemId)
    
        closedState := openState := listenState := establishedState := {
         } closeWaitState := closingState := 0
         
        repeat id from 0 to 3
          case(status[id])
            $00: closedState               |= |< id
                 closedState               &= tcpMask  
            $13: openState                 |= |< id
                 openState                 &= tcpMask                   
            $14: listenState               |= |< id
                 listenState               &= tcpMask
            $17: establishedState          |= |< id
                 establishedState          &= tcpMask
            $18,$1A,$1B: closingState      |= |< id
                         closingState      &= tcpMask
            $1C: closeWaitState            |= |< id
                 closeWaitState            &= tcpMask
            $1D: closingState              |= |< id
                 closingState              &= tcpMask
            $22: udpListen                 |= |< id
                 udpListen                 &= udpMask 
    
        if(lastEstblState <> establishedState)
          value := establishedState
          repeat while value > 0
            tmp := DecodeId(value)
            if(tmp > -1)
              QueueSocket(tmp)
              tmp := |< tmp
              tmp := !tmp
              value &= tmp
          lastEstblState := establishedState
    
        lockclr(debugSemId)
        
        ' Initialize a closed socket 
        if(closedState > 0)
          id := DecodeId(closedState)
          if(id > -1)
            InitializeSocket(id & tcpMask)
        
        'Start a listener on an initialized/open socket   
        if(openState > 0)
          id := DecodeId(openState)
          if(id > -1)
            Socket.Listen(id & tcpMask)
    
        ' Close the socket if the status is close/wait
        ' response processor should close the socket with disconnect
        ' there could be a timeout so we have a forced close.
        ' TODO: CCheck for a port that gets stuck in a closing state
        'if(closeWaitState > 0)
          'id := DecodeId(closeWaitState)
          'if(id > -1)
            'Socket.Close(id & tcpMask)
    
    
    
        'pause(100)
    return
        
    DAT
      approot               byte    "\", 0 
      defaultpage           byte    "index.htm", 0
      logfile               byte    "log.txt", 0
      'binFile               byte    "filename.bin", 0
      FS                    byte    "/", 0
      fn                    byte    "filename=", 0
      doublequote           byte    $22, 0
      crlf                  byte    13, 10, 0
      crlf_crlf             byte    13, 10, 13, 10, 0
      uploadfile            byte    $0[12], 0
      uploadfolder          byte    "uploads", 0
      tempNum               byte    "0000",0
      multipart             byte    "Content-Type: multipart/form-data; boundary=",0
      boundary              byte    $2D, $2D
      boundary1             byte    $0[64]
      'loadme                file    "TogglePin0.binary"                 
    
    {{
    &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
    &#9474;                                                   TERMS OF USE: MIT License                                                  &#9474;                                                            
    &#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
    &#9474;Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation    &#9474; 
    &#9474;files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,    &#9474;
    &#9474;modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software&#9474;
    &#9474;is furnished to do so, subject to the following conditions:                                                                   &#9474;
    &#9474;                                                                                                                              &#9474;
    &#9474;The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.&#9474;
    &#9474;                                                                                                                              &#9474;
    &#9474;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE          &#9474;
    &#9474;WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR         &#9474;
    &#9474;COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,   &#9474;
    &#9474;ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                         &#9474;
    &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
    }}
    
    

    Like I said earlier - it's like pulling teeth. Every step yields more problems..... very frustrating.
  • Mike GMike G Posts: 2,702
    edited 2013-06-26 18:31
    Well, have extra stuff that you're not using... so remove em'

    You could always create a new HttpServer file with only the stuff to get SNTP working.
  • xanatosxanatos Posts: 1,120
    edited 2013-06-26 19:24
    It is working with TEMP_BUFFER set to $200 - but I haven't got a clue to determine how big or small that buffer can be and still let everything work. How do I determine that?

    This is the last thing I'm working on tonight. If I don't get some sleep tonight I'm going to start hallucinating.

    SNTP *seems* to be working. When I run PST and load HTTPServer.spin via an F10, the SNTP Server reports the following to the PST window:
    SNTP Server Sync time:
    (GMT - 1:00) 06/27/2013 01:09:54
    

    And my timestamped email is the correct time, which seems to indicate that the time is being corrected for EST and DST.

    So only one question remains on this (after I check to be sure that the time in my RTC is really from SNTP and not from the last time I set it manually...) That question is How can determine the minimum safe value for TEMP_BUFFER?

    Thanks ... and have a great night!

    Dave
  • xanatosxanatos Posts: 1,120
    edited 2013-06-26 19:39
    UPDATE... still here. While SNTP is working, it is not actually setting the RTC time. The time as last set is in fact the time that remains when SNTP runs on a power-up or reboot of the Spinneret. I tested this by setting the RTC using my web-page RTC setting function, then sending the email - the RTC's manually set date shows.

    Then I F10 to reload HTTPServer.spin. I see the initial messages, and finally, SNTP runs and reports the proper GMT -1 time. And then I send an email - and it shows the time on the originally set schedule, updated to the time intervening since the last email (in other words, it's getting the new time from the buffer not just reporting the same time from previously).

    So now... I get to delve into where the SNTP 1.2.spin program actually engages rtc.writeTime.

    And I'm trying to figure out why the manual web-page time setting function hangs when I have SNTP enabled - if I have these three lines active in the PUB Initialize block:
      GetSntp(3, @tempBuff)
      SNTP.GetTransmitTimestamp(Zone,@tempBuff,@LongHIGH,@LongLOW)
      DisplayHumanTime
    

    Then when I use the web-page time setter, it just hangs the whole server. Comment out those three lines, all works fine. One thing I did notice, however, is that when I set the time manually using the post.htm time setting function - when the Spinneret sends it's every-10-minute email, I see this line where it is printing the conversation log from the Email Sending block:
    250 Message queued
    
    221 Goodbye
    
    p-Alive
    
    Cache-Control: no-cache
    
    
    
    Smonth=06&Sdate=26&Syear=13&Shour=23&Smin=33&Ssec=40&Sday=0&Submit=Click+to+set+time+and+date
    Discon/reset skt: 2
    

    It looks like the data string from the web page's form output is being retained in the rxbuffer (?) - which leads me to believe that I am still doing something wrong when it comes to sending data into the Spinneret.

    Thoughts?

    Current full code (with the three SNTP initialization lines commented out):
    {{
    &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; 
    Copyright (c) 2011 AgaveRobotics LLC.
    See end of file for terms of use.
    
    File....... HTTPServer.spin 
    Author..... Mike Gebhard
    Company.... Agave Robotics LLC
    Email...... mailto:mike.gebhard@agaverobotics.com
    Started.... 11/01/2010
    Updated.... 07/16/2011
    Modified... 06/25/2013 by David Xanatos       
    &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; 
    }}
    
    {
    About:
      HTTPServer is the designed for use with the Spinneret Web server manufactured by Parallax Inc.
    
    Usage:
      HTTPServer is the top level object.
      Required objects:
            &#8226; Parallax Serial Terminal.spin
            &#8226; W5100_Indirect_Driver.spin
            &#8226; S35390A_SD-MMC_FATEngineWrapper.spin
            &#8226; Request.spin
            &#8226; Response.spin
            &#8226; StringMethods.spin
            &#8226; S35390A_RTCEngine.spin 
    
    Change Log:
     
    }
    
    
    CON
      _clkmode = xtal1 + pll16x     
      _xinfreq = 5_000_000
    
      MAX_PACKET_SIZE = $5C0 '1472   '$800 = 2048
      RxTx_BUFFER     = $800         '$600 = 1536
      TEMP_BUFFER     = $200         '$5B4 = 1460 
      TCP_PROTOCOL    = %0001        '$300 = 768 
      UDP_PROTOCOL    = %0010        '$200 = 512 
      TCP_CONNECT     = $04          '$100 = 256                 
      DELAY           = $05
      MAX_TRIES       = $05
      
      #0, DONE, PUT, CD
    
      '' The following are added for the SNTP Object:
    
      'USA Standard Time Zone Abbreviations
      #-10, HST,AtST,_PST,MST,CST,EST,AlST
      # -1, GMT {Day light saving time, we don't have dyalight savings time in AZ}
    
                  
      'USA Daylight Time Zone Abbreviations     <-  No longer used in v1.1
      '#-9, HDT,AtDT,PDT,MDT,CDT,EDT,AlDT
    
      Zone = GMT  '<- Eastern Standard Time =  GMT-5
    
        'W5100 Interface
       #0, W5100_DATA0, W5100_DATA1, W5100_DATA2, W5100_DATA3, W5100_DATA4
       #5, W5100_DATA5, W5100_DATA6, W5100_DATA7, W5100_ADDR0, W5100_ADDR1
      #10, W5100_WR, W5100_RD, W5100_CS, W5100_INT, W5100_RST, W5100_SEN
    
    
      TIME_PORT         = 123
      TIMEOUT_SECS      = 10
                                               
    
    DAT
      mac                   byte    $00, $08, $DC, $16, $F3, $D3     
      subnet                byte    255, 255 ,255, 0
      gateway               byte    192, 168, 1, 1 
      ip                    byte    192, 168, 1, 120
      port                  word    5000
      remoteIp              byte    65, 98, 8, 151 {65.98.8.151}
      remotePort            word    80
      uport                 word    5050 
      emailIp               byte    8, 24, 153, 30    
      emailPort             word    587     ''587     ''110       ''25
      port2                 word    5010
      status                byte    $00, $00, $00, $00   
      rxdata                byte    $0[RxTx_BUFFER]
      txdata                byte    $0[RxTx_BUFFER]
      udpdata               byte    $0[TEMP_BUFFER]
      fileErrorHandle       long    $0
      debug                 byte    $0
      lastFile              byte    $0[12], 0
      closedState           byte    %0000
      openState             byte    %0000
      listenState           byte    %0000
      establishedState      byte    %0000
      closingState          byte    %0000
      closeWaitState        byte    %0000 
      lastEstblState        byte    %0000
      lastEstblStateDebug   byte    %0000
      udpListen             byte    %0000
      tcpMask               byte    %1111
      udpMask               byte    %1000   
      fifoSocketDepth       byte    $0
      fifoSocket            long    $00_00_00_00
      debugSemId            byte    $00
      debugCounter          long    $00
      stringMethods         long    $00
      closingTimeout        long    $00, $00, $00, $00
      udpLen                long    $00
      time                  byte    "00/00/0000 00:00:00", 0
      httpDate              byte    "Wed, 01 Feb 2000 01:00:00 GMT", 0
      globalCache           byte    $1
      dynamicContentPtr     long    @txdata
      tankfile              byte    "tankfarm.csv", 0
      tempBuff              byte    $0[TEMP_BUFFER]
      sntpIp                byte    69, 25, 96, 13 {San Jose CA}
                  
    VAR
      long StackSpace[20]
      byte EEArray[10]                             ' For 24LC256 EEPROM data reads for sending email with data embedded.
      long CKSetArray[7]                           ' For RTC data setting from web interface.
      long longHIGH,longLOW,MM_DD_YYYY,DW_HH_MM_SS ' Expected 4-contigous variables (This is for SNTP Object)
    
    OBJ
      pst           : "Parallax Serial Terminal"
      Socket        : "W5100_Indirect_Driver"
      SDCard        : "S35390A_SD-MMC_FATEngineWrapper"
      Request       : "Request"
      Response      : "Response"
      str           : "StringMethods"
      rtc           : "S35390A_RTCEngine"
      nums          : "Simple_Numbers_plus"    ' "Numbers"
      sntp          : "SNTP 1.2"
    
    PUB Initialize | id, size, st
    
      debug := 1    
      SDCard.Start
      stringMethods := str.Start
      Request.Constructor(stringMethods)
      Response.Constructor(stringMethods, @txdata)
    
      pst.Start(115_200)
      pause(200)
    
      'Mount the SD card
      pst.str(string("Mount SD Card - ")) 
      SDCard.mount(fileErrorHandle)
      pst.str(string("OK",13))
      
      pst.str(string("Start RTC: "))
      rtc.RTCEngineStart(29, 28, -1)
      
      pause(200)
    
      'rtc.writeTime(0, 15, 18, 0, 25, 6, 2013)  ' (second, minute, hour, day, date, month, year)
      'pause(200)
      
      pst.str(FillTime)
        
      'Start the W5100 driver
      if(Socket.Start)
        pst.str(string(13, "W5100 Driver Started", 13))
        pst.str(string(13, "Status Memory Lock ID    : "))
        pst.dec(Socket.GetLockId)
        pst.char(13) 
    
    
      if(debugSemId := locknew) == -1
        pst.str(string("Error, no HTTP server locks available", 13))
      else
        pst.str(string("HTTP Server Lock ID      : "))
        pst.dec(debugSemId)
        pst.char(13)
        
    
      'Set the Socket addresses  
      SetMac(@mac)
      SetGateway(@gateway)
      SetSubnet(@subnet)
      SetIP(@ip)
    
      ' Initailize TCP sockets (defalut setting; TCP, Port, remote ip and remote port)
      repeat id from 0 to 3
        InitializeSocket(id)
        Request.Release(id)
        pause(50)
    
      ' Set all TCP sockets to listen
      pst.char(13) 
      repeat id from 0 to 3 
        Socket.Listen(id)
        pst.str(string("TCP Socket Listener ID   : "))
        pst.dec(id)
        pst.char(13)
        pause(50)
    
      pst.Str(string(13,"Started Socket Monitoring Service", 13))
     
      cognew(StatusMonitor, @StackSpace)
      pause(250)
    
    
      pst.Str(string(13, "Initial Socket States",13))
      StackDump
    
      pst.Str(string(13, "Initial Socket Queue",13))
      QueueDump
    
      pst.str(string(13,"/////////////////////////////////////",13))
    
      'GetSntp(3, @tempBuff)
      'SNTP.GetTransmitTimestamp(Zone,@tempBuff,@LongHIGH,@LongLOW)
      'DisplayHumanTime
    
      
      Main
    
       
    PUB Main | packetSize, id, i, reset, j, temp
      ''HTTP Service
    
      repeat
    
          
         clockandemail   ' Checks the clock and sends email at 10 minute intervals.  Use this for primary
    
         
                                                   ''  Use this for a manual "force email" button?
          dira[22] := 0  '' Sets 22 to input       ''  This block watches the buffered IO on P22 for a 0 signal, and
          if ina[22] == 0                          ''  it sends the email when there is a low pulse.  It also fires off the LED
             dira[23]~~                            ''  just because... why not.
             outa[23] := 1 ''ina[22]
             'TankLog(string("XTM-MAN,01,0,0,0,001,0,06/11/2013 19:00:00,000.00,100.00"))
             pause(DELAY)
             SendTestEmail(2)
             outa[23] := 0 ''ina[22] 
      
    
     
      
        repeat until fifoSocket == 0
        
          bytefill(@rxdata, 0, RxTx_BUFFER)
    
          if(debug)
            pst.str(string(13, "----- Start of Request-----",13))
            pause(DELAY)
          else
            pause(DELAY)
            
          ' Pop the next socket handle 
          id := DequeueSocket
          if(id < 0)
            next
          
          if(debug)
            pst.str(string(13,"ID: "))
            pst.dec(id)
            pst.str(string(13, "Request Count     : "))
            pst.dec(debugCounter)
            pst.char(13)
    
          packetSize := Socket.rxTCP(id, @rxdata)
    
          reset := false
          if ((packetSize < 12) AND (strsize(@rxdata) < 12))
            repeat i from 0 to MAX_TRIES
               'pst.str(string(13,"* Retry *"))
              'Wait for a few moments and try again
              waitcnt((clkfreq/500) + cnt)
              packetSize := Socket.rxTCP(id, @rxdata)
              if(packetSize > 12)
                quit
              if(i == MAX_TRIES)
                'Clean up resource request   
                Request.Release(id)
                Socket.Disconnect(id)
                reset := true
                if(debug)
                  StackDump
                  pst.char(13)
                  QueueDump
                  pst.str(string(13,"* Timeout *",13))
                
          if(reset)
            next
    
          Request.InitializeRequest(id, @rxdata)
          
          if(debug)
            pst.char(13)
            HeaderLine1(id)
          else
            pause(DELAY)
    
          ' Process router
          Dispatcher(id)
    
          'Clean up request resource
          Request.Release(id)
    
          ' This starts the close process -> 0x00
          ' use close to force a close
          Socket.Disconnect(id)
    
          bytefill(@txdata, 0, RxTx_BUFFER)
    
          debugCounter++
    
      GotoMain
    
    PUB SendTestEmail(id) | size, tempMask, wait, stringPointer, tankNum, tankStr, Yr, Mo, Dy, Hr, Mn, Sc
        
        tempMask := tcpMask
        SetTcpSocketMaskById(id, 0)
        
        wait := 200
        
        Socket.Close(id)
        pause(delay)
        InitializeSocketForEmail(id)
        pause(delay)
        
        ' Connect to the mail server
        pst.str(string(13, "Connecting..."))
        
        Socket.Connect(id)
        pause(wait)
        repeat while !Socket.Connected(id)
        
        pst.str(string(13,"Connected... "))
        
        'Send greeting
        StringSend(id, string("EHLO xanatos@xanatos.com", 13, 10))
        pause(wait)
        
    '==============================================================================
        StringSend(id, string("AUTH LOGIN", 13,10))
        pause(wait)
    
    '   'Send user name
        StringSend(id, string("eGFuYXRvc0B4YW5hdG9zLmNvbQ==", 13, 10))   
    '
    '
    '   'Send password
        StringSend(id, string("cGVhY2VhbmR0cmFucXVpbGl0eQ==", 13, 10))  
    '==============================================================================
    
        ' From Address
        StringSend(id, string("MAIL FROM: xanatos@xanatos.com", 13, 10))
        pause(wait)
        
        ' To Address
        StringSend(id, string("RCPT TO: xanatos@xanatos.com", 13, 10))
        pause(wait)
    
        ' To Address
        'StringSend(id, string("RCPT TO: simmons@rsipd.com", 13, 10))       'simmons@rsipd.com  rick.carl@invensys.com
        'pause(wait)
    
        'Start of the email content
        StringSend(id, string("DATA", 13, 10))
        pause(wait)
    
        'Visible From line
        StringSend(id, string("From: xanatos@xanatos.com", 13, 10))
        pause(wait)
    
        'Visible To line
        StringSend(id, string("To: xanatos@xanatos.com", 13, 10))
        pause(wait)
    
        'Subject line
        StringSend(id, string("SUBJECT: GHBW Data", 13, 10))
        pause(wait)
    
        'Mime-Type
        StringSend(id, string("Mime-Version: 1.0", 13, 10))
        pause(wait)
    
        'Content Type
        StringSend(id, string("Content-Type: text/plain; charset=us-ascii", 13, 10, 13, 10))
        pause(wait)
    
        rtc.readTime
        
        Mo := rtc.clockMonth
        Dy := rtc.clockDate
        Yr := rtc.clockYear
        Hr := rtc.clockHour
        Mn := rtc.clockMinute
        Sc := rtc.clockSecond
        
        repeat tankNum from 1 to 32                    ' Increase to 128 for live version!
    
          StringSend(id, string("XTM-MAN,01,0,0,0,"))  ' Hardcoded junk to keep receiving end happy with field assignments
          StringSend(id, nums.decn(tankNum, 3))        ' Dynamically generated Tank Number
          StringSend(id, string(",0,"))                ' More junk
          StringSend(id, nums.decn(Mo, 2))             ' Dynamically obtained Time and Date from RTC.
          StringSend(id, string("/"))
          StringSend(id, nums.decn(Dy, 2))
          StringSend(id, string("/"))
          StringSend(id, nums.decn(Yr, 4))
          StringSend(id, string(" "))
          StringSend(id, nums.decn(Hr, 2))
          StringSend(id, string(":"))
          StringSend(id, nums.decn(Mn, 2))
          StringSend(id, string(":"))
          StringSend(id, nums.decn(Sc, 2))
          StringSend(id, string(","))
          StringSend(id, string("000.00,"))            ' Level Data, or...
          StringSend(id, string("050.00", 13, 10))     ' Level Data.  Not sure yet which field they want live.
          pause(wait)
    
        'Quit conversation
        StringSend(id, string(".", 13, 10))
        pause(wait)
        
        StringSend(id, string("QUIT", 13, 10))
        pause(wait)
        
        pst.str(string(13,"Done",13))
        pst.str(string(13,"Conv. Log",13))
        
        'Display log
        repeat until size := Socket.rxTCP(id, @rxdata)
        pst.str(@rxdata)
        
        pst.str(string(13, "Discon/reset skt: "))
        pst.dec(id)
        pst.char(13)
        
        ' Reset the socket
        Socket.Disconnect(id)
        pause(delay)
        
        ' Reset the tcpMask 
        tcpMask := tempMask
        
        InitializeSocket(id)
        pause(delay)
        
        return
    
    
    
    '=======================================================================================
    PUB clockandemail | mSent      ' mSent 0 means mail NOT sent during this cycle.  1 means mail WAS sent during this cycle.
                
       waitcnt(clkfreq/5+cnt)         
       rtc.readtime
     
       'pst.str(string("            "))   
       'pst.dec(rtc.clockHour)
       'pst.str(string(":"))    
       'pst.dec(rtc.clockMinute)
       'pst.str(string(":"))  
       'pst.dec(rtc.clockSecond)
       'pst.str(string(" ",13 ))
     
       if rtc.clockMinute//10 == 0   ' //5, or //10, or //15, or //20, or //30
           
           if mSent == 0
               waitcnt(clkfreq/5+cnt)   'wait for stuff to stop
               SendTestEmail(2)         'INITIALIZES THE SOCKET AND sends the email
               mSent := 1
               waitcnt(clkfreq/5+cnt)   'wait for stuff to stop
       else
           mSent := 0
              
     return
    '=======================================================================================
    
    PUB GetSntp(id, BufferAddress)| i, size, tempMask', wait
    
        'save current socket state
        tempMask := tcpMask
        SetTcpSocketMaskById(id, 0)
        
        Socket.Close(id)
        pause(delay)
        Socket.Initialize(id, UDP_PROTOCOL, uport, TIME_PORT, @sntpIp)
        pause(delay)
        
        SNTP.CreateUDPtimeheader(BufferAddress,@sntpIp)
    
        repeat 10*TIMEOUT_SECS
          socket.txUDP(id, BufferAddress) '<-- Send the UDP packet
          i := socket.rxUDP(id,BufferAddress)  
          if i == 56
             socket.Disconnect(id)  '<-- At this point we are done, we have
                                        '     the time data and don't need to keep
                                        '     the connection active.
             tcpMask := tempMask        'reset the socket
             InitializeSocket(id)
             pause(delay)
             return 1                   '<- Time Data is ready
          pause(100) '<- if 1000 = 1 sec ; 10 = 1/100th sec X 100 repeats above = 1 sec   
    
        tcpMask := tempMask        'reset the socket
        InitializeSocket(id)
        pause(delay)
        return -1                       '<- Timed out without a response
    
    PUB DisplayHumanTime
        pst.Char(13)
        pst.Char(13)
        pst.Char(13)
        pst.str(string("SNTP Server Sync time:",13))
        pst.str(string("(GMT "))
        if Zone<0
           pst.Char("-")
        else
           pst.Char("+")
        pst.str(string(" ",||Zone+48,":00) "))
        if byte[@MM_DD_YYYY][3]<10
           pst.Char("0")
        pst.dec(byte[@MM_DD_YYYY][3])
        pst.Char("/")
        if byte[@MM_DD_YYYY][2]<10
           pst.Char("0")
        pst.dec(byte[@MM_DD_YYYY][2])
        pst.Char("/")
        pst.dec(word[@MM_DD_YYYY][0])                    
        pst.Char(9)
        if byte[@DW_HH_MM_SS][2]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][2])
        pst.Char(":")
        if byte[@DW_HH_MM_SS][1]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][1])
        pst.Char(":")
        if byte[@DW_HH_MM_SS][0]<10
           pst.Char("0")
        pst.dec(byte[@DW_HH_MM_SS][0])
        pst.Char(13)
    
    PRI GotoMain
      Main
    
    
    PRI InitializeSocketForEmail(id)
        Socket.Initialize(id, TCP_PROTOCOL, port2, emailPort, @emailIp)
        return
    
    PRI InitializeSocket2(id)
      Socket.Initialize(id, TCP_PROTOCOL, port2, remotePort, @remoteIp)
      return
    
    PRI Dispatcher(id)
        ''Do some processing before sending the response
        'if(strcomp(Request.GetFileName(id), string("led.htm")) AND Request.GetDepth(id) == 1)     
        '    if(Led(id))       
         '           return   
        'if(strcomp(Request.GetName(id), string("post")))
        if(strcomp(Request.GetName(id), string("post")) AND strcomp(Request.GetMethod(id), string("POST")))
                Post(id)
        if(strcomp(Request.GetFileName(id), string("aled.htm")) AND Request.GetDepth(id) == 1)     
            SendLedResponse(id)
                    return           
        StaticFileHandler(id)
        return
    
    PRI Post(id) | qstr, Yr, Mo, Dy, Hr, Mn, Sc
        '' Get the post value
        CKSetArray[6] :=  Request.Post(id, string("Syear"))
        CKSetArray[5] :=  Request.Post(id, string("Smonth"))
        CKSetArray[4] :=  Request.Post(id, string("Sdate"))
        CKSetArray[3] :=  Request.Post(id, string("Sday"))
        CKSetArray[2] :=  Request.Post(id, string("Shour"))
        CKSetArray[1] :=  Request.Post(id, string("Smin"))
        CKSetArray[0] :=  Request.Post(id, string("Ssec"))
    
        Sc := str.ToInteger(CKSetArray[0])
        Mn := str.ToInteger(CKSetArray[1])
        Hr := str.ToInteger(CKSetArray[2])
        Dy := str.ToInteger(CKSetArray[4])
        Mo := str.ToInteger(CKSetArray[5])
        Yr := 2000 + str.ToInteger(CKSetArray[6])
        
        rtc.writeTime(Sc, Mn, Hr, 0, Dy, Mo, Yr) 'SET THE RTC (second, minute, hour, day, date, month, year)     
    
        waitcnt(clkfreq/5+cnt)         
    '    rtc.readtime
    '    pst.dec(rtc.clockHour)
    '    pst.str(string(":"))    
    '    pst.dec(rtc.clockMinute)
    '    pst.str(string(":"))  
    '    pst.dec(rtc.clockSecond)
    '    pst.str(string(" ",13 ))
          
        return
    
    PRI SendLedResponse(id) | headerLen, qstr
            '' Get the query string value
            qstr :=  Request.Get(id, string("led"))
      
            '' Exit if there is no querystring
            if(qstr == 0)
              return
    
            
            '' Turn the LED on if the led= value is "on"  
            if (strcomp(string("on"), qstr ))
                    LedStatus(1)
            else
                    LedStatus(0)
            
       '' Build and send the header
            '' Send the value of led= on or off
            headerLen := Response.BuildHeader(Request.GetExtension(id), 200, globalCache)
            Socket.txTCP(id, @txdata, headerLen)
            Socket.txTCP(id, qstr, strsize(qstr))
    
            return
                    
    PRI Led(id) | qstr
        '' Get the query string value
        qstr :=  Request.Get(id, string("led"))
        if (strcomp(string("on"), qstr ))
            LedStatus(1)
        else
            LedStatus(0)
            pst.str(string(" - off",13,10))   
        return
        
    PRI LedStatus(state)
        dira[23]~~
        outa[23] := state
        return
            
    PRI StaticFileHandler(id) | fileSize, i, headerLen, temp, j
      ''Serve up static files from the SDCard
      
      'pst.str(string(13,"Static File Handler",13)) 
      SDCard.changeDirectory(@approot)
      pst.char(13)
      
      'Make sure the directory exists
      ifnot(ChangeDirectory(id))
        'send 404 error
        WriteError(id)
        SDCard.changeDirectory(@approot)
        return
        
      ' Make sure the file exists
      ifnot(FileExists(Request.GetFileName(id)))
        'send 404 error
        WriteError(id)
        SDCard.changeDirectory(@approot)
        return
    
      ' Open the file for reading
      SDCard.openFile(Request.GetFileName(id), "r")
      fileSize := SDCard.getFileSize
    
      'WriteResponseHeader(id)
      'BuildHeader(extension, statusCode, expirer)
      headerLen := Response.BuildHeader(Request.GetExtension(id), 200, globalCache)
      Socket.txTCP(id, @txdata, headerLen)
      
      if fileSize < MAX_PACKET_SIZE
        ' send the file in one packet
        SDCard.readFromFile(@txdata, fileSize)
        Socket.txTCP(id, @txdata, fileSize)
      else
        ' send the file in a bunch of packets 
        repeat
          SDCard.readFromFile(@txdata, MAX_PACKET_SIZE)  
          Socket.txTCP(id, @txdata, MAX_PACKET_SIZE)
          fileSize -= MAX_PACKET_SIZE
          ' once the remaining fileSize is less then the max packet size, just send that remaining bit and quit the loop
          if fileSize < MAX_PACKET_SIZE and fileSize > 0
            SDCard.readFromFile(@txdata, fileSize)
            Socket.txTCP(id, @txdata, fileSize)
            quit
       
          ' Bailout
          if(i++ > 1_000_000)
            WriteError(id)
            quit
         
      SDCard.closeFile
      SDCard.changeDirectory(@approot)
      return
    
    
      
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' SDCard Logger
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI TankLog(logToAppend)
      '' logToAppend:  Pointer to a string of text we'd like to log
      SDCard.changeDirectory(@approot) 
    
      if(FileExists(@tankfile))
        SDCard.openFile(@tankfile, "A")
      else
        SDCard.newFile(@tankfile)
    
      SDCard.writeData(logToAppend, strsize(logToAppend))
      SDCard.writeData(@crlf_crlf, 2)
    
      SDCard.closeFile
    
      return
    
    PRI WriteError(id) | headerOffset
      '' Simple 404 error
      pst.str(string(13, "Write 404 Error",13 ))
      headerOffset := Response.BuildHeader(Request.GetExtension(id), 404, false)
      Socket.txTCP(id, @txdata, headerOffset)
      return
    
      
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Write data to a buffer
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI PushDynamicContent(content)
      ' Write the content to memory
      ' and update the pointer
      bytemove(dynamicContentPtr, content, strsize(content))
      dynamicContentPtr := dynamicContentPtr + strsize(content)
      return
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' directory and file handlers
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''  
    PRI ChangeDirectory(id) | i, found
      'Handle directory structure for this Request
      if(Request.GetDepth(id) > 1)
        repeat i from 0 to Request.GetDepth(id)-2
          'Return if the directory is not found 
          ifnot(FileExists(Request.GetPathNode(id, i)))
            return false
          found := SDCard.changeDirectory(Request.GetPathNode(id, i))
      return true 
    
      
    PRI FileExists(fileToCompare) | filenamePtr
    'Start file find at the top of the list
      SDCard.startFindFile 
      'Verify that the file exists
      repeat while filenamePtr <> 0
        filenamePtr := SDCard.nextFile 
        if(str.MatchPattern(filenamePtr, fileToCompare, 0, false ) == 0 )
          return true
    
      return false
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Time Methods and Formats
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI GetTime(id) | ptr, headerOffset
      ptr := @udpdata
      FillHttpDate
      
      bytemove(ptr, string("<p>"),3)
      ptr += 3
    
      bytemove(ptr, @httpDate, strsize(@httpDate))
      ptr += strsize(@httpDate)
      
      bytemove(ptr, string("</p>"),4)
      ptr += 3  
    
      headerOffset := Response.BuildHeader(Request.GetExtension(id), 200, false)
      Socket.txTCP(id, @txdata, headerOffset)
      StringSend(id, @udpdata)
      bytefill(@udpdata, 0, TEMP_BUFFER)
      
      return
     
     
    PRI FillTime | ptr, num
     'ToString(integerToConvert, destinationPointer)
     '00/00/0000 00:00:00
      ptr := @time
      rtc.readTime
      
    
      FillTimeHelper(rtc.clockMonth, ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockDate, ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockYear, ptr)
      ptr += 5
    
      FillTimeHelper(rtc.clockHour , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockMinute , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockSecond, ptr) 
     
      return @time
    
    
    PRI FillHttpDate | ptr, num, temp
     'ToString(integerToConvert, destinationPointer)
     'Wed, 01 Feb 2000 01:00:00 GMT
      ptr := @httpDate
      rtc.readTime
    
    
      temp := rtc.getDayString
      bytemove(ptr, temp, strsize(temp))
      ptr += strsize(temp) + 2
    
      FillTimeHelper(rtc.clockDate, ptr)
      ptr += 3
    
      temp := rtc.getMonthString
      bytemove(ptr, temp, strsize(temp))
      ptr += strsize(temp) + 1
    
      FillTimeHelper(rtc.clockYear, ptr)
      ptr += 5
    
      FillTimeHelper(rtc.clockHour , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockMinute , ptr)
      ptr += 3
    
      FillTimeHelper(rtc.clockSecond, ptr)
      
      return @httpDate
     
    
    PRI FillTimeHelper(number, ptr) | offset
      offset := 0
      if(number < 10)
        offset := 1
         
      str.ToString(@number, @tempNum)
      bytemove(ptr+offset, @tempNum, strsize(@tempNum))
      
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' SDCard Logger
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI AppendLog(logToAppend)
      '' logToAppend:  Pointer to a string of text we'd like to log
      SDCard.changeDirectory(@approot) 
    
      if(FileExists(@logfile))
        SDCard.openFile(@logfile, "A")
      else
        SDCard.newFile(@logfile)
    
      SDCard.writeData(string(13,10,"----- Start "),14)
      SDCard.writeData(FillTime, 19)
      SDCard.writeData(string(" -----"),6)
      SDCard.writeData(@crlf_crlf, 2)
    
      SDCard.writeData(logToAppend, strsize(logToAppend))
      SDCard.writeData(@crlf_crlf, 2)
      
      SDCard.writeData(string("----- End "),10)
      SDCard.writeData(FillTime, 19)
      SDCard.writeData(string(" -----"),6)
      SDCard.writeData(@crlf_crlf, 2)
    
      SDCard.closeFile
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Memory Management
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI Set(DestAddress, SrcAddress, Count)
      bytemove(DestAddress, SrcAddress, Count)
      bytefill(DestAddress+Count, $0, 1)
      
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Socket helpers
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    
    PRI GetTcpSocketMask(id)
      return id & tcpMask
    
      
    PRI DecodeId(value) | tmp
        if(%0001 & value)
          return 0
        if(%0010 & value)
          return 1
        if(%0100 & value)
          return 2 
        if(%1000 & value)
          return 3
      return -1
    
    
    PRI QueueSocket(id) | tmp
      if(fifoSocketDepth > 4)
        return false
    
      tmp := |< id
      
      'Unique check
      ifnot(IsUnique(tmp))
        return false
        
      tmp <<= (fifoSocketDepth++) * 8
      
      fifoSocket |= tmp
    
      return true
    
    
    PRI IsUnique(encodedId) | tmp
      tmp := encodedId & $0F
      repeat 4
        if(encodedId & fifoSocket)
          return false
        encodedId <<= 8
      return true 
        
    
    PRI DequeueSocket | tmp
      if(fifoSocketDepth == 0)
        return -2
      repeat until not lockset(debugSemId) 
      tmp := fifoSocket & $0F
      fifoSocket >>= 8  
      fifoSocketDepth--
      lockclr(debugSemId)
      return DecodeId(tmp)
    
      
    PRI ResetSocket(id)
      Socket.Disconnect(id)                                                                                                                                 
      Socket.Close(id)
      
    PRI IsolateTcpSocketById(id) | tmp
      tmp := |< id
      tcpMask &= tmp
    
    
    PRI SetTcpSocketMaskById(id, state) | tmp
    '' The tcpMask contains the socket the the StatusMonitor monitors
      tmp := |< id
      
      if(state == 1)
        tcpMask |= tmp
      else
        tmp := !tmp
        tcpMask &= tmp 
        
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' W5100 Helper methods
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI GetCommandRegisterAddress(id)
      return Socket#_S0_CR + (id * $0100)
    
    PRI GetStatusRegisterAddress(id)
      return Socket#_S0_SR + (id * $0100)
    
        
    PRI SetMac(_firstOctet)
      Socket.WriteMACaddress(true, _firstOctet)
      return 
    
    
    PRI SetGateway(_firstOctet)
      Socket.WriteGatewayAddress(true, _firstOctet)
      return 
    
    
    PRI SetSubnet(_firstOctet)
      Socket.WriteSubnetMask(true, _firstOctet)
      return 
    
    
    PRI SetIP(_firstOctet)
      Socket.WriteIPAddress(true, _firstOctet)
      return 
    
    
    
    PRI StringSend(id, _dataPtr)
      Socket.txTCP(id, _dataPtr, strsize(_dataPtr))
      return 
    
    
    PRI SendChar(id, _dataPtr)
      Socket.txTCP(id, _dataPtr, 1)
      return 
    
     
    PRI SendChars(id, _dataPtr, _length)
      Socket.txTCP(id, _dataPtr, _length)
      return 
    
    
    PRI InitializeSocket(id)
      Socket.Initialize(id, TCP_PROTOCOL, port, remotePort, @remoteIp)
      return
    
    PRI InitializeUPDSocket(id)
      Socket.Initialize(id, UDP_PROTOCOL, uport, remotePort, @remoteIp)
      return
    
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    '' Debug/Display Methods
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    PRI QueueDump
      '' Display socket IDs in the queue
      '' ie 00000401 -> socket Zero is next to pop off
      pst.str(string("FIFO["))
      pst.dec(fifoSocketDepth)
      pst.str(string("] "))
      pst.hex(fifoSocket, 8)
    
        
    PRI StackDump | clsd, open, lstn, estb, clwt, clng, id, ulst
      '' This method is purely for debugging
      '' It displays the status of all socket registers
      repeat until not lockset(debugSemId)
      clsd := closedState
      open := openState
      lstn := listenState
      estb := establishedState
      clwt := closeWaitState
      clng := closingState
      ulst := udpListen
      lockclr(debugSemId)
    
      pst.char(13) 
      repeat id from 3 to 0
        pst.dec(id)
        pst.str(string("-"))
        pst.hex(status[id], 2)
        pst.str(string(" "))
        pause(1)
    
      pst.str(string(13,"clsd open lstn estb clwt clng udps", 13))
      pst.bin(clsd, 4)
      pst.str(string("-"))
      pst.bin(open, 4)
      pst.str(string("-"))
      pst.bin(lstn, 4)
      pst.str(string("-"))  
      pst.bin(estb, 4)
      pst.str(string("-"))  
      pst.bin(clwt, 4)
      pst.str(string("-"))  
      pst.bin(clng, 4)
      pst.str(string("-"))  
      pst.bin(ulst, 4)
      pst.char(13)
       
    PRI HeaderLine1(id) | i
      pst.str(Request.GetMethod(id))
      pst.char($20)
    
      i := 0
      repeat Request.GetDepth(id)
        pst.char($2F)
        pst.str(Request.GetPathNode(id, i++))
        
       
    PRI Pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return
    
    
    PRI StatusMonitor | id, tmp, value
    '' StatusMonitor is the heartbeat of the project
    '' Here we monitor the state of the Wiznet 5100's 4 sockets
      repeat
    
        Socket.GetStatus32(@status[0])
    
        ' Encode status register states
        repeat until not lockset(debugSemId)
    
        closedState := openState := listenState := establishedState := {
         } closeWaitState := closingState := 0
         
        repeat id from 0 to 3
          case(status[id])
            $00: closedState               |= |< id
                 closedState               &= tcpMask  
            $13: openState                 |= |< id
                 openState                 &= tcpMask                   
            $14: listenState               |= |< id
                 listenState               &= tcpMask
            $17: establishedState          |= |< id
                 establishedState          &= tcpMask
            $18,$1A,$1B: closingState      |= |< id
                         closingState      &= tcpMask
            $1C: closeWaitState            |= |< id
                 closeWaitState            &= tcpMask
            $1D: closingState              |= |< id
                 closingState              &= tcpMask
            $22: udpListen                 |= |< id
                 udpListen                 &= udpMask 
    
        if(lastEstblState <> establishedState)
          value := establishedState
          repeat while value > 0
            tmp := DecodeId(value)
            if(tmp > -1)
              QueueSocket(tmp)
              tmp := |< tmp
              tmp := !tmp
              value &= tmp
          lastEstblState := establishedState
    
        lockclr(debugSemId)
        
        ' Initialize a closed socket 
        if(closedState > 0)
          id := DecodeId(closedState)
          if(id > -1)
            InitializeSocket(id & tcpMask)
        
        'Start a listener on an initialized/open socket   
        if(openState > 0)
          id := DecodeId(openState)
          if(id > -1)
            Socket.Listen(id & tcpMask)
    
        ' Close the socket if the status is close/wait
        ' response processor should close the socket with disconnect
        ' there could be a timeout so we have a forced close.
        ' TODO: CCheck for a port that gets stuck in a closing state
        'if(closeWaitState > 0)
          'id := DecodeId(closeWaitState)
          'if(id > -1)
            'Socket.Close(id & tcpMask)
    
    
    
        'pause(100)
    return
        
    DAT
      approot               byte    "\", 0 
      defaultpage           byte    "index.htm", 0
      logfile               byte    "log.txt", 0
      'binFile               byte    "filename.bin", 0
      FS                    byte    "/", 0
      fn                    byte    "filename=", 0
      doublequote           byte    $22, 0
      crlf                  byte    13, 10, 0
      crlf_crlf             byte    13, 10, 13, 10, 0
      uploadfile            byte    $0[12], 0
      uploadfolder          byte    "uploads", 0
      tempNum               byte    "0000",0
      multipart             byte    "Content-Type: multipart/form-data; boundary=",0
      boundary              byte    $2D, $2D
      boundary1             byte    $0[64]
      'loadme                file    "TogglePin0.binary"                 
    
    {{
    &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
    &#9474;                                                   TERMS OF USE: MIT License                                                  &#9474;                                                            
    &#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
    &#9474;Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation    &#9474; 
    &#9474;files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,    &#9474;
    &#9474;modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software&#9474;
    &#9474;is furnished to do so, subject to the following conditions:                                                                   &#9474;
    &#9474;                                                                                                                              &#9474;
    &#9474;The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.&#9474;
    &#9474;                                                                                                                              &#9474;
    &#9474;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE          &#9474;
    &#9474;WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR         &#9474;
    &#9474;COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,   &#9474;
    &#9474;ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                         &#9474;
    &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
    }}
    

    Pulling teeth...

    Dave
  • Mike GMike G Posts: 2,702
    edited 2013-06-27 06:16
    xanatos, I'm going to be very blunt... You need to pull up your big boy pants!
    So now... I get to delve into where the SNTP 1.2.spin program actually engages rtc.writeTime.

    The SNTP demo does not set the RCT time nor does it claim to set the RTC time. The code demonstrates how to call an SNTP server and display the results. How SNTP is invoked and used is up to you.
    It looks like the data string from the web page's form output is being retained in the rxbuffer (?) - which leads me to believe that I am still doing something wrong when it comes to sending data into the Spinneret.
    Correct the rxbuffer can contain stale data. It's a buffer - that's how they work! The rxbuffer is accurate after processing the request and before processing the response.

    I realize you are under some stress to get this project done. Give me a call if you need guidance
  • xanatosxanatos Posts: 1,120
    edited 2013-06-27 06:46
    :lol: All my big-boy pants are written in PBASIC, VBscript, JavaScript or HTML/CSS. I have no big boy pants for Spin, that's why I'm here looking like an idiot! :-)

    What I think here is that the huge, cavernous void between how little I know and how much many others on here who are kind enough to help DO know is that to a novice like myself, I look at the code in SNTP 1.2, and realize that a) I do not see anything in it that I recognize as the part that sets the RTC, and b) see enough that I do not understand but that suggests to me that perhaps the setting is being done through a mechanism that is currently unknown to me. I honestly inferred from the title of this post that the code as presented would integrate with HTTPServer, and set the clock.

    I know from experience in teaching in the areas that I do have knowledge that I take a lot of understandings for granted that my student is utterly clueless about. I now find myself formally in that position of utter cluelessness, not even knowing that there is something I don't know that I don't know! :-) Worse yet is when I know so little that I can't even tell that what I DO know is actually what's needed in a given instance...

    So firstly, thank you - I didn't know that the setting of the RTC was up to me, and now that I do, surprisingly, it looks like I may be able to do that just fine! So I have learned something.

    And yes, I am stressing and losing sleep about how long it's taking me to learn this and when I promised it to be ready. So I will take you up on your call offer soon. But first, as usual, I will try to get as far with what I now know on my own before then. And again, thank you very much for your help. I'm sewing those Spin Big Boy pants up a little more every day! :-)
  • xanatosxanatos Posts: 1,120
    edited 2013-06-29 14:50
    I have gotten SNTP 1.2 to work, and it works quite well. I am currently writing a routine to allow the IP Address of the time server to be changed in the event that the time server should go away somehow, and the manual time-setting routing that is initiated from a POST through an HTML file on the SD Card is working as well.

    I have one question now looking back at SNTP 1.2.

    In the CON Block of HTTPServer.spin, we have this set up (among other things, eliminated for clarity):
    VAR
      long longHIGH,longLOW,MM_DD_YYYY,DW_HH_MM_SS ' Expected 4-contigous variables (This is for SNTP Object)
    

    The next time that MM_DD_YYYY and DW_HH_MM_SS shows up is in PUB DisplayHumanTime:
    PUB DisplayHumanTime | Yr, Mo, Dy, Hr, Mn, Sc  
        pst.Char(13)
        pst.Char(13)
        pst.Char(13)
        pst.str(string("SNTP (GMT "))
        if Zone<0
           pst.Char("-")
        else
           pst.Char("+")
           
        pst.str(string(" ",||Zone+48,":00) "))
        
        if byte[@MM_DD_YYYY][3]<10          ' MONTH
           pst.Char("0")
        pst.dec(byte[@MM_DD_YYYY][3])
        pst.Char("/")
        
        if byte[@MM_DD_YYYY][2]<10
           pst.Char("0")       
        pst.dec(byte[@MM_DD_YYYY][2])       ' DATE
        pst.Char("/")
        
        pst.dec(word[@MM_DD_YYYY][0])       ' YEAR             
        pst.Char(9)
        
        
        if byte[@DW_HH_MM_SS][2]<10         ' HOUR
           pst.Char("0")       
        pst.dec(byte[@DW_HH_MM_SS][2])
        pst.Char(":")
        
        if byte[@DW_HH_MM_SS][1]<10         ' MINUTE
           pst.Char("0")       
        pst.dec(byte[@DW_HH_MM_SS][1])
        pst.Char(":")
        
        if byte[@DW_HH_MM_SS][0]<10         ' SECOND
           pst.Char("0")       
        pst.dec(byte[@DW_HH_MM_SS][0])
        
        pst.Char(13)
    
        Sc := byte[@DW_HH_MM_SS][0]
        Mn := byte[@DW_HH_MM_SS][1]
        Hr := byte[@DW_HH_MM_SS][2]
        Dy := byte[@MM_DD_YYYY][2]
        Mo := byte[@MM_DD_YYYY][3]
        Yr := word[@MM_DD_YYYY][0]
    
        rtc.writeTime(Sc, Mn, Hr, 0, Dy, Mo, Yr) 'SET THE RTC (second, minute, hour, day, date, month, year)     
        waitcnt(clkfreq/5+cnt)                   'This is just for testing - setting a wrong time first, then SNTP, and see which emails.
        
    return
    

    What I'm trying to figure out is HOW the time data gets into the DW_HH_MM_SS and MM_DD_YYYY locations. I can't determine if it's getting put in there by an offset, or by writing the strings received from the server to a base location that automatically fills the data into those locations by virtue of the start point and the length of the data received.

    Again - it IS working, I'm just confused on this one issue. I've read in the Propeller manual for BYTE and DAT, and I am still not sure if my thinking is correct on this or if I am - as usual - completely off the mark.

    Thanks!

    Dave

    Including SNTP 1.2.spin here for convenience:
    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.  
    
    }}
    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]  := %11_100_011    'leap,version, and mode
        byte[BufferAddress][9]  := 0              'stratum
        byte[BufferAddress][10] := 0              'Poll   
        byte[BufferAddress][11] := %10010100      '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           = %11           ; alarm condition (clock not synchronized) 
    byte 8=        version        = %011 or %100  ; Version 3 or 4
    byte 8=        Mode           = %011          ; Client        
    byte 9=        stratum        = %00000000     ; unspecified
    byte 10=       Poll           = %00000000     ; = 2^n seconds (maximum interval between successive messages)
    byte 11=       precision      = %10010100     ; -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] & %00000111
        '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] & %00111000)>>3
        '3 - Version 3 (IPv4 only)
        '4 - Version 4 (IPv4, IPv6 and OSI)
    
    PUB GetLI(BufferAddress)
        result := (byte[BufferAddress][8] & %11000000)>>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,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,Long2) 'Let's get some humantime!
         
    PUB HumanTime(Offset,TimeStamp)
    
      'By starting with a smaller number of seconds for this loop stuff, we do the loops less times.
      'Less loop time= faster completion time.
      
      seconds := long[TimeStamp] 
    
      seconds-=3_534_364_800            'Adjust the NTP value for 2012... 3_534_364_800 is NTP value for 2012, Jan 01, 0:00:00 hours.
      seconds+=(Offset*3600)            'Adjust the value again for our time zone.
    
    
      year:=2012                                              'This is our starting year.
      month:=1                                                '...our starting month.
      day:=1                                                  '...our starting day.
      daycnt:=1
      
      repeat while seconds=>86400                             'More than a day's worth of seconds left?
    
        If (month==1) and (seconds=>86400)                    'Month is 1 and more than a day's worth of seconds left?        
                                                            
          if ((daycounter(31))== 31) AND (seconds=>86400)     'Did we max our day value for the month?     
            month:=2                                          'move to next month      
            Day:=0                                            'set first day value to 0
    
        If (month==2) and (seconds=>86400) and (isleapyear(year))   'same as above- leap year??
          
          if ((daycounter(29))== 29) AND (seconds=>86400)
            month:=3   
            Day:=0 
    
        If (month==2) and (seconds=>86400)
          
          if ((daycounter(28))== 28) AND (seconds=>86400)     'same as above- not a leap year??
            month:=3   
            Day:=0
    
        If (month==3) and (seconds=>86400)                    'same for all remaining months
          
          if ((daycounter(31))== 31) AND (seconds=>86400)
            month:=4
            Day:=0
            
        If (month==4) and (seconds=>86400)
          
          if ((daycounter(30))== 30) AND (seconds=>86400)
            month:=5
            Day:=0
            
        If (month==5) and (seconds=>86400)
          
          if ((daycounter(31))== 31) AND (seconds=>86400)
            month:=6
            Day:=0
            
        If (month==6) and (seconds=>86400)
          
          if ((daycounter(30))== 30) AND (seconds=>86400)
            month:=7
            Day:=0
            
        If (month==7) and (seconds=>86400)
          
          if ((daycounter(31))== 31) AND (seconds=>86400)
            month:=8
            Day:=0
            
        If (month==8) and (seconds=>86400)
          
          if ((daycounter(31))== 31) AND (seconds=>86400)
            month:=9
            Day:=0
            
        If (month==9) and (seconds=>86400) 
          
          if ((daycounter(30))== 30) AND (seconds=>86400)
            month:=10
            Day:=0
            
        If (month==10) and (seconds=>86400)
        
          if ((daycounter(31))== 31) AND (seconds=>86400)
            month:=11
            Day:=0
            
        If (month==11) and (seconds=>86400)
          
          if ((daycounter(30))== 30) AND (seconds=>86400)
            month:=12
            Day:=0
            
        If (month==12) and (seconds=>86400)
          
          if ((daycounter(31))== 31) AND (seconds=>86400)
            year:= year+1                                        'Still have more seconds? Increment year and start over.
            month:=1
            Day:=0
    
    '==========================================
      '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 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>1
       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 
                                   
    
    pub daycounter(maxday)
    
            repeat while (seconds=>86400)         
               seconds-=86400                     
               day++
               daycnt++                              
               if day == maxday                   
                 quit                             
    
            return day
    
    pub isleapyear(check)
    
      Case check
    
        2012: return 1
        2016: return 1
        2020: return 1
        2024: return 1
        2028: return 1
        2032: return 1                    
    
  • RforbesRforbes Posts: 281
    edited 2013-06-29 19:00
    Dave,

    Ok... I wrote that particular "HumanTime" method. I wrote it because at the time, there was a minor bug in Beau's original version. I needed a quick fix, so that's what I came up with. That code is NOT very well written, but it does work. Beau has since fixed the bug in his version, and it's better code. I strongly recommend using his. You'd have to go to the google repository for the spinneret and fetch it if you'd rather use it. If you insist on using my "Humantime" method instead of Beau's version, that's fine. It's just a little sloppy.

    Here's how it does what you're asking about:

    In your HTTPServer object, you called GetTransmitTimestamp(Zone,DLS,@rxdata,@LongHIGH,@LongLOW) from one of your methods. Your method probably does not include Zone or DLS, but I'm almost positive it includes @rxdata,@LongHIGH,@LongLOW as its parameters.


    The GetTransmitTimeStamp method in your sntp 1.2 object calls HumanTime(Offset, Long2) at the very end.

    Since you use @LongLOW in your parameters for the GetTransmitTimeStamp method, the ADDRESS of LongLOW is used in that method. It's renamed to Long2. It then passes Long2, which is the ADDRESS of LongLOW to HumanTime.


    When HumanTime is called, it uses Long2, which is the ADDRESS of LongLow to do its magic. It renames Long2, which is the ADDRESS of LongLow to TimeStamp. When it's finished, at the very end, you'll see the following:
    long[TimeStamp][1] := month<<24+day<<16+year   
    long[TimeStamp][2] := dow<<24+hours<<16+minutes<<8+seconds 
    

    This bit of code is saying "put the month/day/year values on the right side of the := into the long which is 1 ADDRESS ahead of the ADDRESS of TimeStamp. Put the hours/minutes/seconds value on the right side of the := into the long which is 2 ahead of the ADDRESS of TimeStamp.

    So. If you look in the VAR section of HTTPServer, you'll see it has a note by those 4 'contiguous' variables.... because if you moved them into a different order, the long variable that is 1 or 2 addresses ahead of the LongLOW variable would be different. And hence, the methods would not work as expected.

    I recommend you study up the @ symbol, and the DAT section. They are quite critical to most of the spinneret code. :) And... don't feel bad- I was at least as confused as you are when I started trying to tackle all this stuff. You'll get there! Just keep at it.

    Robert
  • xanatosxanatos Posts: 1,120
    edited 2013-06-30 08:09
    Hi RForbes! Thanks for the info, Your explanation does confirm that my thinking was on the right track - just that there's a lot of simultaneous things to juggle to hold the total concept of what's going on. My brain is slowly wrapping around it. :-)

    Regarding newer versions of the SNTP Object, the version I have is SNTP 1.2.spin - but the one in the google code repository is v1.1. There are significant differences in how leap years are handled - SNTP 1.1 has IsDST and IsDSTEU, whereas v1.2 does not. I'm planting both in the following two code windows - can you verify that in fact v1.1 is the one that should be used and that v1.2 that I have been using is in fact, older?

    And lastly, just to add more confusion, I just found this one - a Version 2.01, with an update date of 2/2/2013: http://forums.parallax.com/showthread.php/145697-SNTP-Simple-Network-Time-Protocol-BUG-fix-Update - but it does NOT seem to contain *anything* about DST. I'll add that code in below as well, so that all three versions can be compared in one spot! :-)

    Version 1.1 as downloaded from the Google Repository:
    OBJ
    {{
      v1.1 Revision - Added IsDST IsDSTEU for European Union), a method for detection and adjustment for DST
      Currently implemented for GetTransmitTimestamp, but should work for other methods that call HumanTime
    }}
    
    VAR
    
        long   HH,Month,Date,Year
    
    PUB CreateUDPtimeheader(BufferAddress,IPAddr)
      '---------------------------------------------------------------------
      '                     UDP IP Address - 4 Bytes 
      '---------------------------------------------------------------------
        BYTEMOVE(BufferAddress,IPAddr,4)
      '---------------------------------------------------------------------
      '                       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]  := %11_100_011    'leap,version, and mode
        byte[BufferAddress][9]  := 0              'stratum
        byte[BufferAddress][10] := 0              'Poll   
        byte[BufferAddress][11] := %10010100      '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 
      
      {
    leap           = %11           ; alarm condition (clock not synchronized) 
    version        = %011 or %100  ; Version 3 or 4
    Mode           = %011          ; Client        
    stratum        = %00000000     ; unspecified
    Poll           = %00000000     ; = 2^n seconds (maximum interval between successive messages)
    precision      = %10010100     ; -20 (8-bit signed integer)
    rootdelay      = 0             ; 32 bit value
    rootdispersion = 0             ; 32 bit value
    ref id         = "LOCL"        ; four-character ASCII string
    ref time       = 0             ; 64 bit value
    originate time = 0             ; 64 bit value   
    receive time   = 0             ; 64 bit value
    transmit time  = 0             ; 64 bit value
      }
    
    
    PUB GetMode(BufferAddress)
        result := byte[BufferAddress][8] & %00000111
        '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] & %00111000)>>3
        '3 - Version 3 (IPv4 only)
        '4 - Version 4 (IPv4, IPv6 and OSI)
    
    PUB GetLI(BufferAddress)
        result := (byte[BufferAddress][8] & %11000000)>>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][10]
        '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
        Temp1 += byte[BufferAddress][14]<<8 +byte[BufferAddress][15]
        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][16]<<24+byte[BufferAddress][17]<<16
        Temp1 += byte[BufferAddress][18]<<8 +byte[BufferAddress][19]
        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+20,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][24]<<24+byte[BufferAddress][25]<<16
         Temp1 += byte[BufferAddress][26]<<8 +byte[BufferAddress][27]
         long[Long1]:=Temp1
         Temp1 := byte[BufferAddress][28]<<24+byte[BufferAddress][29]<<16
         Temp1 += byte[BufferAddress][30]<<8 +byte[BufferAddress][31]
         long[Long2]:=Temp1     
         'This is the time at which the local clock was
         'last set or corrected, in 64-bit timestamp format.
         HumanTime(Offset,Long1)
    
    PUB  GetOriginateTimestamp(Offset,BufferAddress,Long1,Long2)|Temp1
         Temp1 := byte[BufferAddress][32]<<24+byte[BufferAddress][33]<<16
         Temp1 += byte[BufferAddress][34]<<8 +byte[BufferAddress][35]
         long[Long1]:=Temp1
         Temp1 := byte[BufferAddress][36]<<24+byte[BufferAddress][37]<<16
         Temp1 += byte[BufferAddress][38]<<8 +byte[BufferAddress][39]
         long[Long2]:=Temp1     
         'This is the time at which the request departed the
         'client for the server, in 64-bit timestamp format.
         HumanTime(Offset,Long1)
    
    PUB  GetReceiveTimestamp(Offset,BufferAddress,Long1,Long2)|Temp1
         Temp1 := byte[BufferAddress][40]<<24+byte[BufferAddress][41]<<16
         Temp1 += byte[BufferAddress][42]<<8 +byte[BufferAddress][43]
         long[Long1]:=Temp1
         Temp1 := byte[BufferAddress][44]<<24+byte[BufferAddress][45]<<16
         Temp1 += byte[BufferAddress][46]<<8 +byte[BufferAddress][47]
         long[Long2]:=Temp1     
         'This is the time at which the request arrived at
         'the server, in 64-bit timestamp format.
         HumanTime(Offset,Long1)     
    
    PUB  GetTransmitTimestamp(Offset,BufferAddress,Long1,Long2)|Temp1
         Temp1 := byte[BufferAddress][48]<<24+byte[BufferAddress][49]<<16
         Temp1 += byte[BufferAddress][50]<<8 +byte[BufferAddress][51]
         long[Long1]:=Temp1
         Temp1 := byte[BufferAddress][52]<<24+byte[BufferAddress][53]<<16
         Temp1 += byte[BufferAddress][54]<<8 +byte[BufferAddress][55]
         long[Long2]:=Temp1     
         'This is the time at which the reply departed the
         'server for the client, in 64-bit timestamp format.
         HumanTime(Offset,Long1)
         If IsDST
           HumanTime(Offset+1,Long1)
    
    PUB HumanTime(Offset,TimeStampAddress)|i,Seconds,Days,Years,LYrs,DW,DD,MM,SS
        Seconds := long[TimeStampAddress] + Offset * 3600
        Days    := ((Seconds >>= 7)/675) + 1 '<- Days since Jan 1, 1900
    
        DW      := (Days-1) // 7
        
        Years:=0
        repeat while Days > 365     '<- When done, Days will contain
          Years++                   '   Number of Days THIS year and
          Days -= 365               '   Years will show number of years
                                    '   since 1900.
    
        LYrs := Years / 4           '<- Leap years since 1900
        Year := Years + 1900        '<- Current Year                   
    
        Days -= LYrs                '<- Leap year Days correction
                                    '   for THIS year
        repeat
          repeat i from 1 to 12     '<- Calculate number of days 
            Month := 30             '   in each month.  Stop if
            if i&1 <> (i&8)>>4      '   Month has been reached
               Month += 1
            if i == 2
               Month := 28 
            if Days => Month        '<- When done, Days will contain
               Days -= Month        '   the number of days so far this 
               if Days =< Month     '   month.  In other words, the Date.
                  quit     
        until Days =< Month
        Month := i + 1              '<- Current Month               
        Date  := Days               '<- Current Date
    
    
        SS := long[TimeStampAddress]-(((Years*365)*675)<<7) '<- seconds this year
        SS += Offset * 3600 
        
        MM := SS / 60                        '<- minutes this year
        SS := SS - (MM * 60)                 '<- current seconds
    
        HH := MM / 60                        '<- hours this year
        MM := MM - (HH * 60)                 '<- current minutes
    
        DD := HH / 24                        '<- days this year
        HH := HH - (DD * 24)                 '<- current hour
    
        DD -= LYrs                           '<- Leap year Days correction
                                             '   for THIS year
    
        long[TimeStampAddress][2] := Month<<24+Date<<16+Year
        long[TimeStampAddress][3] := DW<<24+HH<<16+MM<<8+SS                                     
    
    '    DD is redundant but I included it for completion...
    '    If you subtract the number of days so far this year from
    '    DD and add one, you should get today's date.  This is calculated
    '    from another angle above from Days
    PUB IsDST|MarDate,NovDate           ' <- Using North American Rules
    
        case Year
          2021, 2027, 2032, 2038, 2049, 2055, 2060, 2066, 2077, 2083, 2088, 2094:
            MarDate := 14
            NovDate := 7
          2011, 2016, 2022, 2033, 2039, 2044, 2050, 2061, 2067, 2072, 2078, 2089, 2095:
            MarDate := 13
            NovDate := 6
          2017, 2023, 2028, 2034, 2045, 2051, 2056, 2062, 2073, 2079, 2084, 2090:
            MarDate := 12
            NovDate := 5
          2012, 2018, 2029, 2035, 2040, 2046, 2057, 2063, 2068, 2074, 2085, 2091, 2096:
            MarDate := 11
            NovDate := 4
          2013, 2019, 2024, 2030, 2041, 2047, 2052, 2058, 2069, 2075, 2080, 2086, 2097:
            MarDate := 10
            NovDate := 3
          2014, 2025, 2031, 2036, 2042, 2053, 2059, 2064, 2070, 2081, 2087, 2092, 2098:
            MarDate := 9
            NovDate := 2
          2015, 2020, 2026, 2037, 2043, 2048, 2054, 2065, 2071, 2076, 2082, 2093, 2099:
            MarDate := 8
            NovDate := 1
    
      case Month
        4,5,6,7,8,9,10:
          Return true
        3:
          if Date > MarDate        
            Return true
          if Date == MarDate         
            if HH => 2                                      
              Return true
        11:
          if Date < NovDate         
            Return true
          if Date == NovDate
            ifNot HH => 1              
              Return true
    
    PUB IsDSTEU|MarDate,OctDate              ' <- Using European Union Rules
    
        case Year
          2021, 2027, 2032, 2038, 2049, 2055, 2060, 2066, 2077, 2083, 2088, 2094:
            MarDate := 28
            OctDate := 31
          2011, 2016, 2022, 2033, 2039, 2044, 2050, 2061, 2067, 2072, 2078, 2089, 2095:
            MarDate := 27
            OctDate := 30
          2017, 2023, 2028, 2034, 2045, 2051, 2056, 2062, 2073, 2079, 2084, 2090:
            MarDate := 26
            OctDate := 29
          2012, 2018, 2029, 2035, 2040, 2046, 2057, 2063, 2068, 2074, 2085, 2091, 2096:
            MarDate := 25
            OctDate := 28
          2013, 2019, 2024, 2030, 2041, 2047, 2052, 2058, 2069, 2075, 2080, 2086, 2097:
            MarDate := 31
            OctDate := 27
          2014, 2025, 2031, 2036, 2042, 2053, 2059, 2064, 2070, 2081, 2087, 2092, 2098:
            MarDate := 30
            OctDate := 26
          2015, 2020, 2026, 2037, 2043, 2048, 2054, 2065, 2071, 2076, 2082, 2093, 2099:
            MarDate := 29
            OctDate := 25
    
      case Month
        4,5,6,7,8,9:
          Return true
        3:
          if Date > MarDate        
            Return true
          if Date == MarDate        
            if HH => 1                                      
              Return true
        10:
          if Date =< OctDate         
            Return true
          
    

    Version 1.2 as downloaded from the links toward the top of this thread:
    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.  
    
    }}
    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]  := %11_100_011    'leap,version, and mode
        byte[BufferAddress][9]  := 0              'stratum
        byte[BufferAddress][10] := 0              'Poll   
        byte[BufferAddress][11] := %10010100      '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           = %11           ; alarm condition (clock not synchronized) 
    byte 8=        version        = %011 or %100  ; Version 3 or 4
    byte 8=        Mode           = %011          ; Client        
    byte 9=        stratum        = %00000000     ; unspecified
    byte 10=       Poll           = %00000000     ; = 2^n seconds (maximum interval between successive messages)
    byte 11=       precision      = %10010100     ; -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] & %00000111
        '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] & %00111000)>>3
        '3 - Version 3 (IPv4 only)
        '4 - Version 4 (IPv4, IPv6 and OSI)
    
    PUB GetLI(BufferAddress)
        result := (byte[BufferAddress][8] & %11000000)>>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,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,Long2) 'Let's get some humantime!
         
    PUB HumanTime(Offset,TimeStamp)
    
      'By starting with a smaller number of seconds for this loop stuff, we do the loops less times.
      'Less loop time= faster completion time.
      
      seconds := long[TimeStamp] 
    
      seconds-=3_534_364_800            'Adjust the NTP value for 2012... 3_534_364_800 is NTP value for 2012, Jan 01, 0:00:00 hours.
      seconds+=(Offset*3600)            'Adjust the value again for our time zone.
    
    
      year:=2012                                              'This is our starting year.
      month:=1                                                '...our starting month.
      day:=1                                                  '...our starting day.
      daycnt:=1
      
      repeat while seconds=>86400                             'More than a day's worth of seconds left?
    
        If (month==1) and (seconds=>86400)                    'Month is 1 and more than a day's worth of seconds left?        
                                                            
          if ((daycounter(31))== 31) AND (seconds=>86400)     'Did we max our day value for the month?     
            month:=2                                          'move to next month      
            Day:=0                                            'set first day value to 0
    
        If (month==2) and (seconds=>86400) and (isleapyear(year))   'same as above- leap year??
          
          if ((daycounter(29))== 29) AND (seconds=>86400)
            month:=3   
            Day:=0 
    
        If (month==2) and (seconds=>86400)
          
          if ((daycounter(28))== 28) AND (seconds=>86400)     'same as above- not a leap year??
            month:=3   
            Day:=0
    
        If (month==3) and (seconds=>86400)                    'same for all remaining months
          
          if ((daycounter(31))== 31) AND (seconds=>86400)
            month:=4
            Day:=0
            
        If (month==4) and (seconds=>86400)
          
          if ((daycounter(30))== 30) AND (seconds=>86400)
            month:=5
            Day:=0
            
        If (month==5) and (seconds=>86400)
          
          if ((daycounter(31))== 31) AND (seconds=>86400)
            month:=6
            Day:=0
            
        If (month==6) and (seconds=>86400)
          
          if ((daycounter(30))== 30) AND (seconds=>86400)
            month:=7
            Day:=0
            
        If (month==7) and (seconds=>86400)
          
          if ((daycounter(31))== 31) AND (seconds=>86400)
            month:=8
            Day:=0
            
        If (month==8) and (seconds=>86400)
          
          if ((daycounter(31))== 31) AND (seconds=>86400)
            month:=9
            Day:=0
            
        If (month==9) and (seconds=>86400) 
          
          if ((daycounter(30))== 30) AND (seconds=>86400)
            month:=10
            Day:=0
            
        If (month==10) and (seconds=>86400)
        
          if ((daycounter(31))== 31) AND (seconds=>86400)
            month:=11
            Day:=0
            
        If (month==11) and (seconds=>86400)
          
          if ((daycounter(30))== 30) AND (seconds=>86400)
            month:=12
            Day:=0
            
        If (month==12) and (seconds=>86400)
          
          if ((daycounter(31))== 31) AND (seconds=>86400)
            year:= year+1                                        'Still have more seconds? Increment year and start over.
            month:=1
            Day:=0
    
    '==========================================
      '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 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>1
       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 
                                   
    
    pub daycounter(maxday)
    
            repeat while (seconds=>86400)         
               seconds-=86400                     
               day++
               daycnt++                              
               if day == maxday                   
                 quit                             
    
            return day
    
    pub isleapyear(check)
    
      Case check
    
        2012: return 1
        2016: return 1
        2020: return 1
        2024: return 1
        2028: return 1
        2032: return 1
        2036: return 1
        2040: return 1
        2044: return 1
        2048: return 1
        2052: return 1
        2056: return 1
        2060: return 1
        2064: return 1                
    

    Version 2.01 as downloaded from http://forums.parallax.com/showthread.php/145697-SNTP-Simple-Network-Time-Protocol-BUG-fix-Update
    OBJ
    {{
    ******************************************************************
    * SNTP Simple Network Time Protocol                       v2.01  *
    * Author: Beau Schwabe                                           *
    *                                                                *
    * Recognition: Benjamin Yaroch, A.G.Schmidt                      *
    *                                                                *
    * Copyright (c) 2011 Parallax                                    *
    * See end of file for terms of use.                              *
    ******************************************************************
    
    
    Revision History:
    v1      04-07-2011              - File created
    
    v1.01   09-08-2011              - Minor code update to correct days in Month rendering
                                    - and replace bytefill with bytemove for the 'ref-id' string                               
    
    v2      01-29-2013              - Fixed an illusive bug that caused problems around the first of the year
    
    v2.01   02-02-2013              - Logic order error with previous bug fix 
    
    }}
    PUB CreateUDPtimeheader(BufferAddress,IPAddr)
      '---------------------------------------------------------------------
      '                     UDP IP Address - 4 Bytes 
      '---------------------------------------------------------------------
        BYTEMOVE(BufferAddress,IPAddr,4)
      '---------------------------------------------------------------------
      '                       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]  := %11_100_011    'leap,version, and mode
        byte[BufferAddress][9]  := 0              'stratum
        byte[BufferAddress][10] := 0              'Poll   
        byte[BufferAddress][11] := %10010100      '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 
      
      {
    leap           = %11           ; alarm condition (clock not synchronized) 
    version        = %011 or %100  ; Version 3 or 4
    Mode           = %011          ; Client        
    stratum        = %00000000     ; unspecified
    Poll           = %00000000     ; = 2^n seconds (maximum interval between successive messages)
    precision      = %10010100     ; -20 (8-bit signed integer)
    rootdelay      = 0             ; 32 bit value
    rootdispersion = 0             ; 32 bit value
    ref id         = "LOCL"        ; four-character ASCII string
    ref time       = 0             ; 64 bit value
    originate time = 0             ; 64 bit value   
    receive time   = 0             ; 64 bit value
    transmit time  = 0             ; 64 bit value
      }
    
    
    PUB GetMode(BufferAddress)
        result := byte[BufferAddress][8] & %00000111
        '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] & %00111000)>>3
        '3 - Version 3 (IPv4 only)
        '4 - Version 4 (IPv4, IPv6 and OSI)
    
    PUB GetLI(BufferAddress)
        result := (byte[BufferAddress][8] & %11000000)>>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][10]
        '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
        Temp1 += byte[BufferAddress][14]<<8 +byte[BufferAddress][15]
        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][16]<<24+byte[BufferAddress][17]<<16
        Temp1 += byte[BufferAddress][18]<<8 +byte[BufferAddress][19]
        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+20,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][24]<<24+byte[BufferAddress][25]<<16
         Temp1 += byte[BufferAddress][26]<<8 +byte[BufferAddress][27]
         long[Long1]:=Temp1
         Temp1 := byte[BufferAddress][28]<<24+byte[BufferAddress][29]<<16
         Temp1 += byte[BufferAddress][30]<<8 +byte[BufferAddress][31]
         long[Long2]:=Temp1     
         'This is the time at which the local clock was
         'last set or corrected, in 64-bit timestamp format.
         HumanTime(Offset,Long1)
    
    PUB  GetOriginateTimestamp(Offset,BufferAddress,Long1,Long2)|Temp1
         Temp1 := byte[BufferAddress][32]<<24+byte[BufferAddress][33]<<16
         Temp1 += byte[BufferAddress][34]<<8 +byte[BufferAddress][35]
         long[Long1]:=Temp1
         Temp1 := byte[BufferAddress][36]<<24+byte[BufferAddress][37]<<16
         Temp1 += byte[BufferAddress][38]<<8 +byte[BufferAddress][39]
         long[Long2]:=Temp1     
         'This is the time at which the request departed the
         'client for the server, in 64-bit timestamp format.
         HumanTime(Offset,Long1)
    
    PUB  GetReceiveTimestamp(Offset,BufferAddress,Long1,Long2)|Temp1
         Temp1 := byte[BufferAddress][40]<<24+byte[BufferAddress][41]<<16
         Temp1 += byte[BufferAddress][42]<<8 +byte[BufferAddress][43]
         long[Long1]:=Temp1
         Temp1 := byte[BufferAddress][44]<<24+byte[BufferAddress][45]<<16
         Temp1 += byte[BufferAddress][46]<<8 +byte[BufferAddress][47]
         long[Long2]:=Temp1     
         'This is the time at which the request arrived at
         'the server, in 64-bit timestamp format.
         HumanTime(Offset,Long1)     
    
    PUB  GetTransmitTimestamp(Offset,BufferAddress,Long1,Long2)|Temp1
         Temp1 := byte[BufferAddress][48]<<24+byte[BufferAddress][49]<<16
         Temp1 += byte[BufferAddress][50]<<8 +byte[BufferAddress][51]
         long[Long1]:=Temp1
         Temp1 := byte[BufferAddress][52]<<24+byte[BufferAddress][53]<<16
         Temp1 += byte[BufferAddress][54]<<8 +byte[BufferAddress][55]
         long[Long2]:=Temp1     
         'This is the time at which the reply departed the
         'server for the client, in 64-bit timestamp format.
         HumanTime(Offset,Long1)
         
    PUB HumanTime(Offset,TimeStampAddress)|i,Seconds,Days,Years,LYrs,DW,DD,HH,MM,SS,Month,Date,Year
        Seconds := long[TimeStampAddress] + Offset * 3600
        Days    := ((Seconds >>= 7)/675) + 1 '<- Days since Jan 1, 1900 ... divide by 86,400 and add 1
    
        DW      := (Days-1) // 7
        
        Years := Days / 365         '   Number of Days THIS year and
        Days -= (Years * 365)       '   number of years since 1900.
    
        LYrs := Years / 4           '<- Leap years since 1900
        Year := Years + 1900        '<- Current Year                   
    
        Days -= LYrs                '<- Leap year Days correction
                                    '   for THIS year
        repeat
          repeat i from 1 to 12     '<- Calculate number of days 
            Month := 30             '   in each month.  Stop if
             if i&1 <> (i&8)>>3     '   Month has been reached
               Month += 1
            if i == 2
               Month := 28 
            if Days =< Month        '<- When done, Days will contain
               quit                 '   the number of days so far this 
            if Days > Month         '   month.  In other words, the Date.
               Days -= Month     
    
    {
            if Days > Month         '<- When done, Days will contain
               Days -= Month        '   the number of days so far this 
            if Days =< Month        '   month.  In other words, the Date.
               quit     
    }
    
        until Days =< Month
        Month := i                  '<- Current Month               
        Date  := Days               '<- Current Date
    
    
        SS := long[TimeStampAddress] + Offset * 3600
        SS := SS -(((Years*365)*675)<<7) '<- seconds this year
             
        MM := SS / 60                        '<- minutes this year
        SS := SS - (MM * 60)                 '<- current seconds
    
        HH := MM / 60                        '<- hours this year
        MM := MM - (HH * 60)                 '<- current minutes
    
        DD := HH / 24                        '<- days this year
        HH := HH - (DD * 24)                 '<- current hour
    
        DD -= LYrs                           '<- Leap year Days correction
                                             '   for THIS year
    
        long[TimeStampAddress][2] := Month<<24+Date<<16+Year
        long[TimeStampAddress][3] := DW<<24+HH<<16+MM<<8+SS                                     
    
    '    DD is redundant but I included it for completion...
    '    If you subtract the number of days so far this year from
    '    DD and add one, you should get today's date.  This is calculated
    '    from another angle above from Days
         
    
  • RforbesRforbes Posts: 281
    edited 2013-06-30 09:19
    Dave,

    There is no way I'm going to go through all three objects and tell you which one to use. I didn't write them, I don't track the revisions and I don't use them. I don't want to sound rude about that but these objects are all "open source" and can be modified by anyone, at any time, for any reason. They're not guaranteed to work correctly, and I don't have the skill or time to give them a "stamp of approval" that has any real meaning.

    Having said that, my recommendation would be to use version 2.01 because I "think" Beau has fixed his code bug in that version. I haven't used it, and I haven't looked through it, so it's just a recommendation based on my knowledge that both Mike G and Beau Schwabe are top-notch, and they are on the ball.

    Concerning leap years and DST, I can refer you to this post. You're welcome to use/modify/laugh at the code, but it's only posted as a conceptual piece. You may or may not be able to use it. It gives a good concept for implementing DST.


    http://forums.parallax.com/showthread.php/146075-Spinneret-Web-Server-with-SNTP-RTC-Sync-and-DHCP-IP-Renewal?p=1176615#post1176615

    I'd like to offer one small bit of advice at this point:

    You seem to be trying to use the spinneret as something other than a server. That's great, and it's very doable. But Mike G's object is written so that it functions as a general server, not a client. It appears that you're trying to tailor it to do very specific things, and you might be better off by taking a step back, figuring out which objects you do/don't need and starting fresh. I've seen that you have hacked up a lot of the pre-written objects, and sometimes this gets so overwhelming that it's almost impossible to work out bugs that were either already present, or introduced with your own changes.
  • xanatosxanatos Posts: 1,120
    edited 2013-06-30 09:48
    Hi Robert,

    Not sounding rude at all, I completely understand and am grateful for whatever guidance I can get. It's better to ask and possibly get useful info than not ask and miss it altogether.

    While it seems that I am trying to get the Spinneret to do a million different things, it's probably just the effect of my asking many questions on things I am trying to learn about that are related to what I am actually trying to do. In actuality, the only functions that the Spinneret is being called upon to do are:

    1. Serve up a single web page that allows the "administrators" of the system to see the time to which the RTC is set, and to provide a manual method of changing that time should it ever become necessary, as well as a method for changing the IP Address of the reference SNTP TIme Server. (ALL FUNCTION WORKING)

    2. To run some version of SNTP to accomplish the above automated time setting functions once each time the system is powered up (FUNCTIONS WORKING)

    3. To automatically send an email every 10 minutes (hence the requirement for the RTC) containing up to 128 datalines with each line showing a one-byte sensor number, a reading consisting of 3 bytes each (XXX . XX, assuming the "." is a byte), and the time/date stamp (again, requiring the RTC). (BASE FUNCTION WORKING - haven't gotten to acquiring the data yet)

    4. To collect the data from an outside hardware/microcontroller system (a BS2px driven system that runs all the 74HC4514 and 74138 data selectors, CD4066 analog switches and other acquisition circuitry - working perfectly and currently just storing the data in an EEPROM via I2C), which consists of a one-byte sensor number, one-byte "0-100" number, and one-byte ".00 to .99" number. (NEXT)

    That's it. And the only reason I'm sending an email with the data and not GETting it, is because we are sending the data to a third-party company who is large and dictates to us how we will present our data to them... and they want it emailed. They have some sort of in-house system that has preassigned data fields and automatically parses the incoming email into the appropriate spots in their system.

    So while it sounds like I'm asking the server to do something other than serve... in actuality, aside from the part where I have it grab the sensor numbers and level data from the BS2px side, I'm really ONLY asking it to be a server.

    Again, thanks for your help. When I took on this project, it never occurred to me just how MUCH different the Spinneret would be from programming the other stuff I program (Stamps, server-side and client-side scripting for web, etc) and so I figured two months would be more than ample time. I was wrong. That date is looming FAST. And the largest physical piece, the data acquisition side of things, with the most circuitry, went together in about two weeks. The Spinneret side, the smallest bit but most important, since it's the transport mechanism of the data - has taken me a Month SO FAR.

    I would have used the PINK Server, but that can only send emails of 64 bytes total... :-) On the bright side, I sure am learning a LOT, and hopefully I can continue that on after this project and start becoming as much of a fan of the Propeller as many people on these forums are.

    Dave
  • xanatosxanatos Posts: 1,120
    edited 2013-06-30 12:54
    For reference - and for anyone following this thread, I am now using SNTP Version 2.01 - it does seem to be the latest and greatest, and it is working well. It was a drop-in replacement for Version 1.2 and other than the OBJ block in HTTPServer.spin's reference to SNTP 2.01, no other modification was needed. Version 2.01 seems to have a few bug fixes in it and a much more sophisticated method for handling leap years - actually calculating them rather than using a hard-coded list.

    Dave
  • xanatosxanatos Posts: 1,120
    edited 2013-06-30 17:07
    Gentlemen, thank you. I have completed the "administrative interface" for this project, thanks to your help and in-depth understanding of things that I'm only just beginning to grasp. The system currently is getting the time set every time the system is powered up (which should only be a few times a month); has a web page based system for manually setting the system time, just in case; for changing the Time Server IP address, also just in case; for manually forcing the data-email to be sent if they need the remote data visualization matrix to be updated sooner than 10 minutes (when they need to offload from a tanker, for example); and of course, a link to my tech support contact info :-)

    Before I started with the SNTP part of this, I had only 74 longs free - I now have 262. I also understand an enormous amount more than when I started.

    I can't imagine where people like me would be without the kind of knowledge and assistance that's on this board. And patience... :-)

    Thanks again.
  • oodesoodes Posts: 131
    edited 2013-11-21 07:18
    Hello Guys,
    It was a drop-in replacement for Version 1.2 and other than the OBJ block in HTTPServer.spin's reference to SNTP 2.01, no other modification was needed


    My question is with the line from 'GetsntpTime' from the Http Server. The Sntp v1.1 is working fine with MIke's Http Server

    sntp.CreateUDPtimeheader(BufferAddress,@sntpIp) 
    

    On the Sntp v1.1 this is a valid but when i run the Sntp v2.01 is only looking for the Buffer address and not the IP of the Time server
    sntp.CreateUDPtimeheader(BufferAddress) 
    

    Where is v2.01 getting the IP address from? I cant seem to get it working.

    I mentioned I had the sntp v1.1 working , well its always an hour behind the correct time. I assuming this has something got to do with
    DSTEU as my offset always needs to be (offset +1) to return me the correct time.
    PUB  GetTransmitTimestamp(Offset,BufferAddress,Long1,Long2)|Temp1
         Temp1 := byte[BufferAddress][48]<<24+byte[BufferAddress][49]<<16
         Temp1 += byte[BufferAddress][50]<<8 +byte[BufferAddress][51]
         long[Long1]:=Temp1
         Temp1 := byte[BufferAddress][52]<<24+byte[BufferAddress][53]<<16
         Temp1 += byte[BufferAddress][54]<<8 +byte[BufferAddress][55]
         long[Long2]:=Temp1     
         'This is the time at which the reply departed the
         'server for the client, in 64-bit timestamp format.
         HumanTime(Offset,Long1)      
         If IsDST
           HumanTime(Offset+1,Long1)
    
    
    If i adjust this to HumanTime(Offset+1,Long1) like in the if statement below it its fine but should this not be the case only if I'm not in GMT
  • Mike GMike G Posts: 2,702
    edited 2013-11-21 08:31
    I modified version 2 a bit in the repo for use with the latest Spinneret libraries.

    Uncomment the the lines of code below and update the method signature for use in the old deprecated code base.
    PUB CreateUDPtimeheader(BufferAddress)
      '---------------------------------------------------------------------
      '                     UDP IP Address - 4 Bytes 
      '---------------------------------------------------------------------
        'BYTEMOVE(BufferAddress,IPAddr,4)
      '---------------------------------------------------------------------
      '                       UDP Header - 4 Bytes 
      '---------------------------------------------------------------------
        'byte[BufferAddress][4] := 0
        'byte[BufferAddress][5] := 123 '<- Port Address 
        'byte[BufferAddress][6] := 0 
        'byte[BufferAddress][7] := 48  '<- Header + Packet
    
  • oodesoodes Posts: 131
    edited 2013-11-21 09:40
    Cheers Mike
    method signature for use in the old deprecated code base

    Am I using an old HttpServer? is there a more recent one? This is the repo link i have

    https://code.google.com/p/spinneret-web-server/source/browse/#svn%2Ftrunk%2FMultiSocketServer_MikeG
  • Mike GMike G Posts: 2,702
    edited 2013-11-21 10:16
    HttpServer is the name of the old web server. WebServer_W5100.spin is the new version. We've been over this a few times...
  • oodesoodes Posts: 131
    edited 2013-11-21 11:26
    Don't recall that been mentioned. Knew there was an older version in the multisocket zip but though the google repo Http version was the newer one. That makes more sense now :D

    Thanks for the help
  • Mike GMike G Posts: 2,702
    edited 2013-11-21 11:53
    There was a point you were using the new libraries, Serial to Ethernet. Unless, I'm thinking of someone else.
  • oodesoodes Posts: 131
    edited 2013-11-21 12:11
    No that was me alright:D . Ah right I was using those new library spin files for doing a Serial to Ethernet connection with the spinneret for another project but I hadnt ventured into the world of the web server at that stage. I began using the files on ServerBeer doing the tutorials after that and thats where I must have crossed back. The http server is functioned well for me and most of the recent work i did was on this. Is there any major reason I shouldnt use this old code as the Webserver_5100 looks a lot different from first glance.
    Hardly any tutorials or application notes for the new WebServer is there? :D
Sign In or Register to comment.