Emailing with the Spinneret - useful code sections
xanatos
Posts: 1,120
I've been working to get the Spinneret to send an email successfully, and after much messing with WireShark and a Telnet client, it works.
The following are some useful notes that I have found in some other posts, and a few that I think are a new contribution here, but if someone else did mention all of these, please let us know. Because I haven't found this information summarized succinctly in a single location, I thought it might be useful to do so. Hpoefully it will speed others' development with the Spinneret.
First, here's the full code of HTTPServer.spin as I've modified it. Aside from just sending an email, this code will also fire off the email when P22 (J6 pin 1) is brought low by any external means - be it a switch, a sensor, or another microcontroller.
Many, many thanks go to Mike Gebhard for his creation of HTTPServer.spin, without which I would be hopelessly lost. I've been a Basic Stamp guy for years, and can basically make a stamp do just about anything - but propeller and spin I am a complete novice at, despite attempting to learn them for a few years. Because of Mike's work, and the contributions of so many forum members, even I have been able to make good headway in this project.
Following the main code are snippets with descriptions of why they are included.
Before you can make any email work, you'll need your email server's IP Address. There are a lot of services on the net (like MXToolbox.com) that will give you what appears to be your mailserver's IP - but not. I've found that the best thing to do is to dowload and install Wireshark, then have it capture packets while you have your email client refresh the inbox. You'll see your ACTUAL mailserver IP address go by. Use that.
Also, look in your mail account properties to determine the port that you're using for send and receive (SMTP and POP). They may not be default, as mine were not. Without these numbers being correct, nothing will work.
Be sure to also set up your local IP and gateway for your local network.
On to emailing...
First - the email parts that are new.
The above code block goes right after the repeat of the PUB Main method. This block sets up pin 22 as an input, and watches for it to go low. When it does, it sends the email.
Next are the items I found had to be present in the email sending method SendTestEmail(2)
First, the ELHO must contain your email address.
Immediately following that should be AUTH LOGIN all by itself. Then the user name and password - which must be in Base64 encoding. Then the MAIL FROM and RCPT TO lines contain your email, and your recipient's email, respectively.
Now the above lines I've seen referenced, but the reference suggested that these might be omitted and could be used to spoof an email - which I suppose is true, but here's why I think they're necessary: Without them, the From column doesn't show up in the received email IF you get it - and several emails I tried at Gmail rejected the email as spam without them. So, the DATA line comes first, then the From and To lines contain the same emails as the MAIL FROM and RCPT TO lines. Then the SUBJECT.
The next two lines shown above also seem to play a role in allowing an email to go through successfully - the Mime-Version and Content-Type, followed by the CR's and LFs (13s and 10s). With this block in place, all the addresses I've tried sending to have successfully received the email, and it looks proper in their email client's window, with From and Subject lines displayed and the mail not thrown out for spam, etc.
---
The above HTTPServer.spin has a lot of things in it from http://spinneret.servebeer.com:5000 and many of them may be unnecessary in your application, but they have been very useful in helping me understand how things work in the Spinneret.
More to follow as I learn.
The following are some useful notes that I have found in some other posts, and a few that I think are a new contribution here, but if someone else did mention all of these, please let us know. Because I haven't found this information summarized succinctly in a single location, I thought it might be useful to do so. Hpoefully it will speed others' development with the Spinneret.
First, here's the full code of HTTPServer.spin as I've modified it. Aside from just sending an email, this code will also fire off the email when P22 (J6 pin 1) is brought low by any external means - be it a switch, a sensor, or another microcontroller.
Many, many thanks go to Mike Gebhard for his creation of HTTPServer.spin, without which I would be hopelessly lost. I've been a Basic Stamp guy for years, and can basically make a stamp do just about anything - but propeller and spin I am a complete novice at, despite attempting to learn them for a few years. Because of Mike's work, and the contributions of so many forum members, even I have been able to make good headway in this project.
Following the main code are snippets with descriptions of why they are included.
{{ ───────────────────────────────────────────────── 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 ───────────────────────────────────────────────── }} { 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 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, 34, 144, 33 emailPort word 554 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-manchester.csv", 0 VAR long StackSpace[20] OBJ pst : "Parallax Serial Terminal" Socket : "W5100_Indirect_Driver" SDCard : "S35390A_SD-MMC_FATEngineWrapper" Request : "Request" Response : "Response" str : "StringMethods" rtc : "S35390A_RTCEngine" 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) 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 ''GetMyIp(3) ''clockandemail repeat 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] SendTestEmail(2) outa[23] := 0 ''ina[22] else outa[23] := 0 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 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 to mail server")) Socket.Connect(id) pause(wait) repeat while !Socket.Connected(id) pst.str(string(13,"Connected... Talking with mail server")) '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("baSe64GibbERiSh_GoesInhErE==", 13, 10)) ' ' ' 'Send password StringSend(id, string("baSe64GibbERiSh_GoesInhErE==", 13, 10)) '============================================================================== ' From Address StringSend(id, string("MAIL FROM: your@address.com", 13, 10)) pause(wait) ' To Address StringSend(id, string("RCPT TO: their@address.com", 13, 10)) pause(wait) 'Start of the email content StringSend(id, string("DATA", 13, 10)) pause(wait) StringSend(id, string("From: your@address.com", 13, 10)) pause(wait) StringSend(id, string("To: their@address.com", 13, 10)) pause(wait) 'Subject line StringSend(id, string("SUBJECT: Email from the Spinneret", 13, 10)) pause(wait) StringSend(id, string("Mime-Version: 1.0", 13, 10)) pause(wait) StringSend(id, string("Content-Type: text/plain; charset=us-ascii", 13, 10, 13, 10)) pause(wait) 'Email body StringSend(id, string("If you're reading this message, you're seeing an email generated by the embedded server - The Spinneret - that will be sending emails to Centeron.", 13, 10)) 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,"Conversation Log",13)) 'Display log repeat until size := Socket.rxTCP(id, @rxdata) pst.str(@rxdata) pst.str(string(13, "Disconnect and reset socket: ")) 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 'SET THE CLOCK ' (second, minute, hour, day, date, month, year) rtc.writeTime(00, 10 , 20, 2, 10, 6, 2013) 'SET THE RTC repeat 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.CLOCKSECOND==40 ' ' waitcnt(clkfreq/5+cnt) 'wait for stuff to stop ' SendTestEmail(2) ' INITIALIZES THE SOCKET AND sends the email ' waitcnt(clkfreq/5+cnt) 'wait for stuff to stop '======================================================================================= 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.GetName(id), string("led"))) Led(id) if(strcomp(Request.GetName(id), string("post"))) Post(id) StaticFileHandler(id) return PRI Post(id) | qstr '' Get the post value qstr := Request.Post(id, string("led")) if (strcomp( string("on"), qstr )) LedStatus(1) pst.str(string(" - on",13,10)) else LedStatus(0) pst.str(string(" - off",13,10)) 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 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 test 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 ions 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. │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ }}
Before you can make any email work, you'll need your email server's IP Address. There are a lot of services on the net (like MXToolbox.com) that will give you what appears to be your mailserver's IP - but not. I've found that the best thing to do is to dowload and install Wireshark, then have it capture packets while you have your email client refresh the inbox. You'll see your ACTUAL mailserver IP address go by. Use that.
Also, look in your mail account properties to determine the port that you're using for send and receive (SMTP and POP). They may not be default, as mine were not. Without these numbers being correct, nothing will work.
Be sure to also set up your local IP and gateway for your local network.
gateway byte 192, 168, 1, 1 ip byte 192, 168, 1, 120 port word 5000 emailIp byte 8, 34, 155, 32 emailPort word 554 ' 'NOT DEFAULT !!! port2 word 5010
On to emailing...
First - the email parts that are new.
PUB Main | packetSize, id, i, reset, j, temp ''HTTP Service ''GetMyIp(3) ''clockandemail repeat 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] SendTestEmail(2) outa[23] := 0 ''ina[22] else outa[23] := 0
The above code block goes right after the repeat of the PUB Main method. This block sets up pin 22 as an input, and watches for it to go low. When it does, it sends the email.
Next are the items I found had to be present in the email sending method SendTestEmail(2)
'Send greeting StringSend(id, string("EHLO your@email.com", 13, 10)) pause(wait) '============================================================================== StringSend(id, string("AUTH LOGIN", 13,10)) pause(wait) ' 'Send user name StringSend(id, string("bAse64EncoDEd_GibbEriSh_GoeS_HeRE==", 13, 10)) ' ' ' 'Send password StringSend(id, string("bAse64EncoDEd_GibbEriSh_GoeS_HeRE==", 13, 10)) '============================================================================== ' From Address StringSend(id, string("MAIL FROM: your@email.com", 13, 10)) pause(wait) ' To Address StringSend(id, string("RCPT TO: their@email.com", 13, 10)) pause(wait)
First, the ELHO must contain your email address.
Immediately following that should be AUTH LOGIN all by itself. Then the user name and password - which must be in Base64 encoding. Then the MAIL FROM and RCPT TO lines contain your email, and your recipient's email, respectively.
'Start of the email content StringSend(id, string("DATA", 13, 10)) pause(wait) StringSend(id, string("From: your@email.com", 13, 10)) pause(wait) StringSend(id, string("To: their@email.com", 13, 10)) pause(wait) 'Subject line StringSend(id, string("SUBJECT: Email from the Spinneret", 13, 10)) pause(wait) StringSend(id, string("Mime-Version: 1.0", 13, 10)) pause(wait) StringSend(id, string("Content-Type: text/plain; charset=us-ascii", 13, 10, 13, 10)) pause(wait)
Now the above lines I've seen referenced, but the reference suggested that these might be omitted and could be used to spoof an email - which I suppose is true, but here's why I think they're necessary: Without them, the From column doesn't show up in the received email IF you get it - and several emails I tried at Gmail rejected the email as spam without them. So, the DATA line comes first, then the From and To lines contain the same emails as the MAIL FROM and RCPT TO lines. Then the SUBJECT.
The next two lines shown above also seem to play a role in allowing an email to go through successfully - the Mime-Version and Content-Type, followed by the CR's and LFs (13s and 10s). With this block in place, all the addresses I've tried sending to have successfully received the email, and it looks proper in their email client's window, with From and Subject lines displayed and the mail not thrown out for spam, etc.
---
The above HTTPServer.spin has a lot of things in it from http://spinneret.servebeer.com:5000 and many of them may be unnecessary in your application, but they have been very useful in helping me understand how things work in the Spinneret.
More to follow as I learn.
Comments
Haha... thanks for the info!! Some good tips for me.
Robert
I have connected to my mail server( Using the wireshark I located the email ip an port number :thumb: ) but do I find out the Base64 encoding of my username and password as I'm trying to use my gmail account?
Reagrds
DEs
- Start WireShark logging.
- Start a client email application; Outlook, Firebird etc...
- Sent an email
When you look at the logs, you do not see the username and password?"Conversation Log" on the PST
Des
Have you tried using Telnet to emulate what your SPIN logic is doing?
Been on this website for awhile. Found lots of good info. I usually can find answers to my problems by quarry. This time, I'm finding a particular situation difficult. I have attached a spin file that was originally generated by Mike G (WOW, you know your stuff; if you are as good of a guitar player as a spinneret programmer; I want to see you "LIVE". Anyway,I seem to be having the same problem as "oodes" (see post 8). I modified his spin code. Seem that when I attempt to send an e-mail from the spinneret (to my gmail or home account), pst hangs at "Conversation Log" (does not proceed further). The destination does not receive the e-mail. I suspect that it hangs at "repeat until size := Socket.rxTCP(id, @rxdata)". Not sure why. I know that my e-mail server ip and port are correct (based on wireshark). Any suggestions would be welcome. BTW, I am using windows 8.1 and "windows mail". I am also using a computer that is connected via a wireless router. The spinneret is connected to the router via a cat5; (should my computer be connected, via cat5, instead of wireless?). I did "comment out" the "user name" and "password" lines. Was under the impression that you could still send out an e-mail without "user name" and "password". BTW, is "user name" your e-mail address? Also, do I need to modify my "spam" or "pop-up" settings? One more thing... I have not tried to "Telnet to emulate what your SPIN logic is doing". Windows 8 is a real pain... If Telnet is necessary, I will try and locate hyperterminal in windows 8. Thanks and Happy Halloween.
The file attached to post #9 uses port 465. Port 465 uses SMTPS which is an SSL connection. Most likely, that's why the code hangs. Yes, credential (username/password) may be required. It depends on the mail service.
Use telnet to step through sending an email. The steps are the same for the Spinneret. Remember the Spinneret does not encrypt messages.
http://www.wikihow.com/Send-Email-Using-Telnet
http://technet.microsoft.com/en-us/library/aa995718(v=exchg.65).aspx
You are the man! Used telnet to step through the process. Found out that my e-mail server wanted to see HELO vice EHLO. Also, used an on-line utility to encrypt my log-in and password (base 64). Everything worked. Thanks! Rock-on!