Emailing with the Spinneret - useful code sections

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!