Shop OBEX P1 Docs P2 Docs Learn Events
Emailing with the Spinneret - useful code sections — Parallax Forums

Emailing with the Spinneret - useful code sections

xanatosxanatos Posts: 1,120
edited 2013-11-01 14:51 in Accessories
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.

{{
───────────────────────────────────────────────── 
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"                 

{{
&#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
&#9474;                                                   TERMS OF USE: MIT License                                                  &#9474;                                                            
&#9500;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9508;
&#9474;Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation    &#9474; 
&#9474;files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,    &#9474;
&#9474;modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software&#9474;
&#9474;is furnished to do so, subject to the following conditions:                                                                   &#9474;
&#9474;                                                                                                                              &#9474;
&#9474;The above copyright notice and this permission notice shall be included in all copies or substantial ions of the Software.&#9474;
&#9474;                                                                                                                              &#9474;
&#9474;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE          &#9474;
&#9474;WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR         &#9474;
&#9474;COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,   &#9474;
&#9474;ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                         &#9474;
&#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
}}



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

  • RforbesRforbes Posts: 281
    edited 2013-06-14 14:25
    Hey xanatos,

    Haha... thanks for the info!! Some good tips for me. :)

    Robert
  • NWCCTVNWCCTV Posts: 3,629
    edited 2013-06-14 17:47
    Many, many thanks go to Mike Gebhard for his creation of HTTPServer.spin, without which I would be hopelessly lost
    I would most definitely have to second that!!!
  • Mike GMike G Posts: 2,702
    edited 2013-06-14 18:43
    :blush: Thank you for the kind words.
  • oodesoodes Posts: 131
    edited 2013-10-22 02:20
    Hello ,
    Immediately following that should be AUTH LOGIN all by itself. Then the user name and password - which must be in Base64 encoding
    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
  • Mike GMike G Posts: 2,702
    edited 2013-10-22 03:28
    Let me see if I understand...
    • 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?
  • oodesoodes Posts: 131
    edited 2013-10-22 05:42
    Unless I'm looking in the wrong places I cant see it. I used a base 64 encoding converter(do these work?) and entered my username and password but it still stops in the same position. Connects to the mail server but then hangs at
    "Conversation Log" on the PST

    Des
  • Mike GMike G Posts: 2,702
    edited 2013-10-22 07:57
    There's not much I can do without the source code.

    Have you tried using Telnet to emulate what your SPIN logic is doing?
  • NAVFAC10NAVFAC10 Posts: 23
    edited 2013-10-31 16:16
    To All,
    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.
  • Mike GMike G Posts: 2,702
    edited 2013-11-01 05:19
    AFAIK, gamil uses TLS to encrypt out going email. My code does not handle TLS or SSL encryption.

    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
  • NAVFAC10NAVFAC10 Posts: 23
    edited 2013-11-01 13:43
    Mike,
    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!
  • Mike GMike G Posts: 2,702
    edited 2013-11-01 14:10
    Cool, glad you got it working!
  • oodesoodes Posts: 131
    edited 2013-11-01 14:51
    Mike is defo the man , must give that another go
Sign In or Register to comment.