Go Down

Topic: Email attachments from arduino as an SMTP client (Read 9523 times) previous topic - next topic

ManicDee

There's no state required, SMTP is a very simple protocol and you can pre-program the entire client side of the conversation, only waiting to see "OK" from the server and dropping the connection if anything looks different to what you expect.

The only problem with trying to use the Arduino for SMTP will be those servers that insist on using SSMTP or SMTP/SSL. Base64 encoding is simple. SMTP conversation is trivial. Ethernet communication is a little harder. I expect SSL requires more data in the form of keys to be held in memory than the Arduino has memory. That might make an interesting experiment for some time in the future.

The obvious workaround is to have a plain-text SMTP server living on the network that the Arduino will connect to, which then forwards the email to wherever it's supposed to go. Of course there are probably "better" ways of getting data off the Arduino, but SMTP is certainly a convenient one (the message goes straight from the Arduino to your mail inbox) and IMHO is the simplest conversation to handle. Other options include FTP or HTTP, but both those protocols are far more complex than SMTP. FTP requires multiple connections and requires the Arduino to listen for incoming connections - no thanks! HTTP will require Base64 encoding, and involves more header generation and a more complex conversation.

I'll be fiddling with my Arduino tonight, let's see if I can't get a simple Base64 encoder working. Of course I'm currently limited to encoding about 400 bytes of data since I don't have an SD card interface yet (it's in the mail). So 400 bytes for input leads to about 530 bytes of output, which ends up leaving 70 bytes of RAM free (on my Uno I'll have more, but you might be using something different). With an SD card (or other external storage), the Base64 encoding operation only needs to use 7.25 bytes of RAM (3 bytes input, 4 bytes output, 2 bits for length of input), "some" memory for an input buffer, "some" memory for an output buffer, and lots of reading and writing over the SD Card interface to fill the input buffer and empty the output buffer.

And then there's the "clever" option of having one buffer for input and output, with the input starting 1/4 of the way through the buffer, overwriting the buffer with the output as it is generated (since we don't need the input data that we've already encoded).

There's also the question of how to get a camera, an Ethernet shield, and an SD Card shield connected to the Arduino at the same time. OH THE WIRING! OH THE HUMANITY!

Razorblade

#16
Jul 28, 2011, 12:36 pm Last Edit: Jul 28, 2011, 12:50 pm by Razorblade Reason: 1

Am I invisible? :smiley-red:

Seems like yes, I am.

Is this a joke? Are you really not reading me? Why the h**l are you still talking about how difficult it would be, etc... when I've written functional examples with Arduino of both sending e-mail and base64 encoding, and demonstrated that with 25 lines of code it's all done?
Arduino Uno (R2fix) / Duemilanove (328p)
Ethernet Shield SD (v5)

ManicDee

Is this a joke? Are you really not reading me? Why the h**l are you still talking about how difficult it would be, etc... when I've written functional examples with Arduino of both sending e-mail and base64 encoding, and demonstrated that with 25 lines of code it's all done?


No you're not and yes indeed you have. Apologies for missing that post.

Razorblade

#18
Jul 28, 2011, 05:08 pm Last Edit: Jul 28, 2011, 05:09 pm by Razorblade Reason: 1
Sorry for the anger, but I was getting worried.


This would be the whole program. Reads picture.jpg from microSD card, sends an e-mail with picture.jpg attachment:
Code: (example.ino) [Select]
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>

byte MAC[]={0x,0x,0x,0x,0x,0x};  // Your MAC
File picture; Client client;

static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
void encodeblock(unsigned char in[3],unsigned char out[4],int len) {
 out[0]=cb64[in[0]>>2]; out[1]=cb64[((in[0]&0x03)<<4)|((in[1]&0xF0)>>4)];
 out[2]=(unsigned char) (len>1 ? cb64[((in[1]&0x0F)<<2)|((in[2]&0xC0)>>6)] : '=');
 out[3]=(unsigned char) (len>2 ? cb64[in[2]&0x3F] : '=');
}
void encode() {
 unsigned char in[3],out[4]; int i,len,blocksout=0;
 while (picture.available()!=0) {
   len=0; for (i=0;i<3;i++) { in[i]=(unsigned char) picture.read(); if (picture.available()!=0) len++; else in[i]=0; }
   if (len) { encodeblock(in,out,len); for(i=0;i<4;i++) client.write(out[i]); blocksout++; }
   if (blocksout>=19||picture.available()==0) { if (blocksout) client.print("\r\n");  blocksout=0; }
 }
}

void setup() {
 Ethernet.begin(MAC); delay(2000);
 pinMode(10,OUTPUT); SD.begin(4);
}

void loop() {
 picture=SD.open("picture.jpg",FILE_READ);
 client.connect("smtp.mail.yahoo.com",25);
 client.print("HELO\nAUTH PLAIN\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n");  // x: base64 encoded login: \0foo@yahoo.com\0password
 client.print("MAIL FROM:<foo@yahoo.com>\nRCPT TO:<bar@fakemail.com>\nDATA\nFrom: \"Foo\" <foo@yahoo.com>\r\nTo: bar@fakemail.com\r\nSubject: Picture attached\r\n");
 client.print("Content-Type: image/jpeg; name=\"picture.jpg\"\r\nContent-Disposition: attachment; filename=\"picture.jpg\"\r\nContent-Transfer-Encoding: base64\r\n\r\n");
 encode();
 client.print("\r\n.\r\nQUIT\n");
 client.stop();
  picture.close();
 while(1);
}


Takes less than 30 seconds to send a 50 KiB picture.
Arduino Uno (R2fix) / Duemilanove (328p)
Ethernet Shield SD (v5)

primate

Thanks for sharing RazorBlade! It's quite interesting to see that the Ethernet shield does not have a fixed MAC address which is really weird and makes it susceptible to MAC collissions (though I doubt anybody is doing MAC-based routing much anymore). What will probably happen is that we end up with everyone using all 00-00-00-00-00-00 as their MAC. I wonder if it might be an issue if you have multiple devices with the same MAC address on the same subnet though.

Also, does the Ethernet shield have existing libraries for DNS lookup and all that?

Finally, what's the bottleneck in this system? Ethernet is really fast so why would sending a 50KB picture take 30 seconds?

Sorry for hi-jacking this thread with n00b questions.

PaulS

Quote
Finally, what's the bottleneck in this system? Ethernet is really fast so why would sending a 50KB picture take 30 seconds?

Not ethernet on the Arduino. Besides, this thread started with sending the picture using a cell phone module.


Razorblade

#22
Jul 28, 2011, 07:29 pm Last Edit: Jul 28, 2011, 07:32 pm by Razorblade Reason: 1
What will probably happen is that we end up with everyone using all 00-00-00-00-00-00 as their MAC. I wonder if it might be an issue if you have multiple devices with the same MAC address on the same subnet though.
'Official' Ethernet shield comes with a sticker with an unique MAC written. It starts with 90-A2-DA-
The problem with multiple Arduinos is the same hostname "WIZnet", but it can be changed in the Ethernet library (Dhcp.h).

Also, does the Ethernet shield have existing libraries for DNS lookup and all that?
Yeah. I'm usign Arduino 1.0-beta1, which comes with DNS and DHCP support.

Finally, what's the bottleneck in this system? Ethernet is really fast so why would sending a 50KB picture take 30 seconds?.
Getting an IP (~4 seconds), reading each byte from the SD (~40 microseconds each: 6 s), SMTP traffic and load.... don't know exactly.

Besides, this thread started with sending the picture using a cell phone module.

? O_o
I am aware of the fact that it is possible to send emails to the SMTP server using the arduino ethernet shield. I want to send picture attachments along with the mail.
Arduino Uno (R2fix) / Duemilanove (328p)
Ethernet Shield SD (v5)

btsp

can you use the code above that razor blade posted above for txt files?

i placed the code with .txt instead of jpgs

but when i run it i get this

connected
220 to5email2.gprs.rogers.com ESMTP server (InterMail vM.7.09.00.04 201-2219-102-106-20090410) ready Fri, 12 Aug 2011 13:28:53 -0400
250 to5email2.gprs.rogers.com
250 Sender <camarduino@gmx.com> Ok
250 Recipient <cam_the_man007@hotmail.com> Ok
500 Command unknown: 'CONTENT-TYPE:;'
500 Command unknown: 'CONTENT-DISPOSITION:'
500 Command unknown: 'CONTENT-TRANSFER-ENCODING:'
500 Command unknown: ''
500 Command unknown: 'BWVVDYBTZW93IG1LBW=='
500 Command unknown: '.'
221 to5email2.gprs.rogers.com ESMTP server closing connection

any insight?

primate

If I remember correctly, I believe any Headers (starting from CONTENT-TYPE) is actually part of the mesage content, and needs to come after the DATA command - but I must admit I'm a bit rusty on this.

Seems like you are only printing the responses from the server. It would help diagnostics if we can also see the outbound SMTP commands to the server too.

btsp

#25
Aug 15, 2011, 05:17 pm Last Edit: Aug 15, 2011, 05:34 pm by btsp Reason: 1
This is the code i am running with the headers after data, which sends an email but no attachment just places those lines of code in the email body.

the results i posted above is when the "DATA" line is removed and the subject


Code: [Select]

#include <Ethernet.h>
#include <SPI.h>
#include <SD.h>



byte mac[] = { 0, 0, 0, 0, 0, 0 }; // Arduino's artificial mac address
byte ip[] = { 0, 0, 0, 0 }; // my ip
byte server[] = { 0, 0, 0, 0 }; // my smtp server ip
int time = 5000;
int wait = 2000;

static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

File datatxt;
Client client(server, 25);


void setup()
{
delay(time);

Ethernet.begin(mac, ip);
Serial.begin(9600);
delay(1000);

pinMode(10,OUTPUT);
SD.begin(4);

Serial.println("connecting...");

if (client.connect()) {
                       
                         
                       Serial.println("connected");
 
                       client.println("HELO itismeletschat"); /* say hello (statement after helo is needed but irrelevant)*/
                         delay(wait); /* wait for a response */

                       client.println("MAIL From: camarduino@gmx.com"); /* identify sender, this should be the same as the smtp server you are using */
                         delay(wait); /* wait for a response */

                       client.println("RCPT To: cameroncollie@gmail.com"); /* identify recipient */
                       delay(wait); /* wait for a response */

                       client.println("DATA");
                         delay(wait); /* wait for a response */
                       
                       datatxt =SD.open("data.txt",FILE_READ);
                       client.println("Subject: Picture attached");
                       client.print("Content-Type: txt; name=\"data.txt\"\r\nContent-Disposition: attachment; filename=\"data.txt\"\r\nContent-Transfer-Encoding: base64\r\n\r\n");
                       
                       client.println("."); /* end email */
               
                       client.println("QUIT"); /* terminate connection */
                       delay(wait); /* wait for a response */
                       client.stop();
                       datatxt.close();
                       
 
} else {
  Serial.println("connection failed");
}
}

void loop()
{
while (client.available()) {
  char c = client.read();
  Serial.print(c);
}

if (!client.connected()) {
  Serial.println();
  Serial.println("disconnecting.");
  client.stop();
  for(;;)
    ;
}
}




void encodeblock(unsigned char in[3],unsigned char out[4],int len) {
 out[0]=cb64[in[0]>>2]; out[1]=cb64[((in[0]&0x03)<<4)|((in[1]&0xF0)>>4)];
 out[2]=(unsigned char) (len>1 ? cb64[((in[1]&0x0F)<<2)|((in[2]&0xC0)>>6)] : '=');
 out[3]=(unsigned char) (len>2 ? cb64[in[2]&0x3F] : '=');
}

void encode() {
 unsigned char in[3],out[4];
 int i,len,blocksout=0;
 
 while (datatxt.available()!=0) {
   len=0;
     for (i=0;i<3;i++){
           in[i]=(unsigned char) datatxt.read();
               if (datatxt.available()!=0) len++;
                     else in[i]=0;
     }
     if (len){
         encodeblock(in,out,len);
          for(i=0;i<4;i++) client.write(out[i]);
               blocksout++; }
     if (blocksout>=19||datatxt.available()==0){
         if (blocksout) client.print("\r\n");  blocksout=0;
     }
  }
}

primate

Hmmm, the only thing I can think of is that \r might not be an allowed character in the SMTP protocol, so instead of \r\n (which is CR-LF, where CR is only relevant for screen display), you can use \n only. Try breaking down the combined lines in the one print() call into separate println() commands and you might be able get more meaningful diagnostics.

Just my 2 cents' worth.

btsp

Well i just tried that code again,
it decided to work this time, now i just need to send the contents of the txt file, it didn't have any contents.
but i can send a txt file.

btsp

oh! nevermind, i missed calling the function "encode();"

Everything is good. Thanks

btsp

#29
Aug 15, 2011, 08:48 pm Last Edit: Aug 15, 2011, 08:54 pm by btsp Reason: 1
If i want to send multiple attachments, do i have to use boundaries?
which i guess the next question is, what are boundaries and how do i use them?

Go Up