Combining Mike G's webserver objects and Beau Schwabes' SNTP Demo
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
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
if you take a look at the changelog (httpserver file) you can see this;
Just download the files at the google code pages.
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)
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 blockVAR 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.
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...
{{ ───────────────────────────────────────────────── 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 ───────────────────────────────────────────────── }} { 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: • Parallax Serial Terminal.spin • W5100_Indirect_Driver.spin • S35390A_SD-MMC_FATEngineWrapper.spin • Request.spin • Response.spin • StringMethods.spin • 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" {{ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ TERMS OF USE: MIT License │ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │ │files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │ │modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│ │is furnished to do so, subject to the following conditions: │ │ │ │The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│ │ │ │THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │ │WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │ │COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ │ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ }}
Thanks!
Dave
COM ... TEMP_BUFFER = $300 DAT ... tempBuff byte $0[TEMP_BUFFER]
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... :-)
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
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.
{{ ───────────────────────────────────────────────── 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 ───────────────────────────────────────────────── }} { 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: • Parallax Serial Terminal.spin • W5100_Indirect_Driver.spin • S35390A_SD-MMC_FATEngineWrapper.spin • Request.spin • Response.spin • StringMethods.spin • 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" {{ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ TERMS OF USE: MIT License │ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │ │files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │ │modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│ │is furnished to do so, subject to the following conditions: │ │ │ │The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│ │ │ │THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │ │WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │ │COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ │ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ }}
Like I said earlier - it's like pulling teeth. Every step yields more problems..... very frustrating.
You could always create a new HttpServer file with only the stuff to get SNTP working.
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
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):
{{ ───────────────────────────────────────────────── 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 ───────────────────────────────────────────────── }} { 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: • Parallax Serial Terminal.spin • W5100_Indirect_Driver.spin • S35390A_SD-MMC_FATEngineWrapper.spin • Request.spin • Response.spin • StringMethods.spin • 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" {{ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ TERMS OF USE: MIT License │ ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │ │files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │ │modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│ │is furnished to do so, subject to the following conditions: │ │ │ │The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│ │ │ │THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │ │WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │ │COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ │ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ }}
Pulling teeth...
Dave
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.
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
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! :-)
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
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.
Robert
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
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.
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
Dave
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.
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 GMTUncomment 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
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
Thanks for the help
Hardly any tutorials or application notes for the new WebServer is there?