Thursday, October 11, 2012

RemoteCamera

This is an Arduino-based camera that sends images wirelessly to your computer. In addition to the Grove base shield - used to connect the camera module, a button and an LED light to the Arduino - we need an SD card shield to save the photos we take, and a Bluetooth shield to send the images off.

A Processing sketch on the computer receives the sent picture, saves it to the hard drive and displays it in the program window. In addition to using the hardware button connected to the Arduino, photos can be taken directly from the computer using the on-screen button or the space bar.

This camera will be part of a garden explorer robot that I'm building, inspired by the Mars Curiosity rover. I'll be adding a time-lapse feature and motion trigger.

Parts:
-Ardiuno Uno R3
-Seeed SD Card Shield
-Seeed Bluetooth Shield
-Grove Base Shield
-Grove Serial JPEG Camera
-Grove Button
-Grove LED
-Grove 4-Pin Cables
-Battery Pack
Suppliers:
Trossen Robotics
Epic Tinker
Seeed Studios

Make sure your portName in the Processing sketch matches the blueToothName in Arduino. And, please make sure this name sticks out - don't just name them "Bluetooth" - or the automatic selector might open the wrong port.

The Arduino sends out a series of 3-letter commands to let Processing know how to update the on-screen status and when to start and stop saving the received jpeg ("CAM" when the camera has finished taking its picture, "PIC" when it starts sending jpeg data and "EOF" at the end of the file). I plan on having error messages sent this way, too.

Photos are saved sequentially in a folder called "rcamera" on the SD card. The Processing sketch creates a new time-stamped folder for each session. These are also kept in "rcamera" within the remotePhoto sketch folder.


Arduino:
 #include <SD.h>  
 #include <SoftwareSerial.h>
  
 File photoFile, textFile;
  
 const int blueToothRx = 6;  
 const int blueToothTx = 7;  
 SoftwareSerial blueToothSerial(blueToothRx,blueToothTx);
 String blueToothName = "DreaduinoBluetooth";
  
 const int photoButtonPin = 5;  
 const int ledPin = 4;  
 const int sdCardPin = 10;               // cs pin of sd card shield
  
 const int photoBufferSize = 96;  
 unsigned int photoSize = 0;  
 int readData = 0;  
 boolean firstPhoto = true;  
 int photoNumber = 0;  
 char photoName[] = "rcamera/pic00000.jpg";  

 char cameraReset[] = {  
  0x56,0x00,0x26,0x00};                  // reset camera  
 char cameraCapture[] = {     
  0x56,0x00,0x36,0x01,0x00};             // take picture  
 char cameraContinue[] = {        
  0x56,0x00,0x36,0x01,0x02};             // take next picture  
 char cameraGetSize[] = {  
  0x56,0x00,0x34,0x01,0x00};             // read jpg file size  
 char cameraGetData[] = {                // read jpg data  
  0x56,0x00,0x32,0x0c,0x00,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a};

   
 void setup()  
 {  
  Serial.begin(115200);
   
  pinMode(blueToothRx, INPUT);  
  pinMode(blueToothTx, OUTPUT);  
  pinMode(photoButtonPin,INPUT);  
  pinMode(ledPin,OUTPUT);  
  pinMode(sdCardPin,OUTPUT);
   
  blueToothSetup();  
  sdCardSetup();  
  checkLastFile();  
  readLastFile();  
  cameraSetup();  
  ledFlash();   
 }   


 void loop()  
 {   
  readPhotoPin();  
  readBlueTooth();   
 }  
 void readPhotoPin()   
 {   
  if(digitalRead(photoButtonPin) == HIGH){  
   delay(50);                                //debounce  
   if(digitalRead(photoButtonPin) == HIGH){  
    takePhoto();    
   }  
  }   
 }

  
 void readBlueTooth()  
 {   
  if (blueToothSerial.available()) {   
   readData = blueToothSerial.read();  
   if (readData == 80)takePhoto();   
  }  
 }  


 void takePhoto()   
 {  
  if(firstPhoto) {  
   cameraCaptureImage();  
   firstPhoto = false;  
  }  
  else cameraContinueCapture();
  
  ledFlash();  

  cameraReadData(); 
 
  ledFlash();  
 
  sendPhoto(); 
 
  ledFlash();   
 }    


 void savePhoto(File &photoFile,int toBeReadSize)  
 {  
  char readSize = 0;  
  for(char i = 0;i < 5;i ++){      //read "ready to send" signal  
   Serial.read();  
  }  
  while(readSize < toBeReadSize){  //read and store the jpeg data  
   photoFile.write(Serial.read());  
   readSize++;  
  }  
  for(char i = 0;i < 5;i ++){      //read "sussessfully sent" signal  
   Serial.read();  
  }  
 }  


 void sendPhoto()  
 {   
  Serial.println("sending photo");      
  blueToothSerial.print("PIC"); 
 
  File photoFile = SD.open(photoName);
  
  if (photoFile) {  
   ledOn();  
   while (photoFile.position() < photoFile.size()) {  
    blueToothSerial.write(photoFile.read());     
   }  
   photoFile.close();
    
   Serial.println("photo sent");      
   blueToothSerial.print("EOF");
  
   saveLastFile(); 
 
   ledOff();   
  }   
  else {  
   Serial.println("error sending photo");  
  }           
 }  


 ////////////////////////////////////////////////////////////////////////////  
 // bluetooth  


 void blueToothSetup()  
 {  
  blueToothSerial.begin(115200);  
  blueToothSerial.print("\r\n+STBD=115200\r\n ");   
  blueToothSerial.print("\r\n+STWMOD=0\r\n");                 // set bluetooth to work in slave mode   
  blueToothSerial.print("\r\n+STNA="+blueToothName+"\r\n");   // set bluetooth name  
  blueToothSerial.print("\r\n+STOAUT=1\r\n");                 // permit paired device to connect  
  blueToothSerial.print("\r\n+STAUTO=0\r\n");                 // auto-connection should be forbidden here  
  delay(2000);                                                // required delay  
  blueToothSerial.print("\r\n+INQ=1\r\n");                    // make bluetooth inquirable   
  Serial.println("dreaduino bluetooth is inquirable");   
  delay(2000);                                                // required delay  
  blueToothSerial.flush();  
 }

  
 ////////////////////////////////////////////////////////////////////////////  
 // sd card  


 void sdCardSetup()  
 {   
  Serial.println("initializing sd card"); 
  
  if (!SD.begin(sdCardPin)) {  
   Serial.print("initialzation failed");  
   return;  
  }   
  Serial.println("sd initialization done");   
 }  
 void checkLastFile()   
 {  
  if (!SD.exists("rcamera/lastfile.txt")) {  
   SD.mkdir("/rcamera");  
   textFile = SD.open("rcamera/lastfile.txt", FILE_WRITE);  
   textFile.seek(0);    
   textFile.write(photoName);  
   textFile.close();   
  }    
 } 

 
 void readLastFile()  
 {  
  textFile = SD.open("rcamera/lastfile.txt", FILE_READ);  
  for (int i=0; i < 16; i++){  
   photoName[i] = textFile.read();     
  } 
  textFile.close(); 
  photoNumber = ((photoName[11]-48)*10000);  
  photoNumber = photoNumber + ((photoName[12]-48)*1000);  
  photoNumber = photoNumber + ((photoName[13]-48)*100);  
  photoNumber = photoNumber + ((photoName[14]-48)*10);  
  photoNumber = photoNumber + ((photoName[15]-48));      
  photoNumber ++;   
 } 

 
 void saveLastFile()  
 {  
  textFile = SD.open("rcamera/lastfile.txt", FILE_WRITE);  
  if(!textFile){  
   Serial.println("failed to open text file");  
  }  
  else{  
   textFile.seek(0);    
   textFile.write(photoName);  
  }  
  textFile.close();   
 }

  
 ////////////////////////////////////////////////////////////////////////////  
 // serial camera 

 
 void cameraSetup()  
 {   
  sendCameraCmd(cameraReset,4);  
  delay(1000);  
  while(Serial.available() > 0)Serial.write(Serial.read());  
  Serial.println("camera initialization done");  
 } 

 
 void cameraCaptureImage()  
 {   
  sendCameraCmd(cameraCapture,5);  
  delay(50);  
  while(Serial.available()){  
   Serial.read();  
  }  
 }  


 void cameraContinueCapture()  
 {  
  sendCameraCmd(cameraContinue,5);  
  delay(50);  
  while(Serial.available() > 0){  
   Serial.write(Serial.read());  
  }  
 }  


 void cameraReadData()  
 {  
  unsigned int photoSize;  
  sendCameraCmd(cameraGetSize,5);  
  delay(50);  
  for(char i = 0; i < 7; i++){  
   Serial.read();  
  }  
  photoSize = 0;  
  int high = Serial.read();  
  photoSize |= high ;  
  int low = Serial.read();  
  photoSize = photoSize << 8 ;  
  photoSize |= low ;
  
  unsigned int addr = 0;  
  cameraGetData[8] = 0;  
  cameraGetData[9] = 0;
  unsigned int count = photoSize / photoBufferSize;  
  char tail = photoSize % photoBufferSize;  
  cameraGetData[13] = photoBufferSize;  

  photoName[11] = photoNumber/10000 + '0';  
  photoName[12] = photoNumber/1000 + '0';  
  photoName[13] = photoNumber/100 + '0';  
  photoName[14] = photoNumber/10 + '0';  
  photoName[15] = photoNumber%10 + '0'; 
 
  Serial.println("saving photo");      
  blueToothSerial.print("CAM"); 
 
  photoFile = SD.open(photoName, FILE_WRITE);  
  
  ledOn();
  
  if(!photoFile){  
   Serial.println("failed to open photo file");  
  }  
  else{  
   for(char i = 0; i < count; i++){      //get and save count*photoBufferSize data  
    sendCameraCmd(cameraGetData,16);  
    delay(10);  
    savePhoto(photoFile, photoBufferSize);  
    addr += photoBufferSize;  
    cameraGetData[8] = addr >> 8;  
    cameraGetData[9] = addr & 0x00FF;  
   }  
   cameraGetData[13] = tail;               //get rest of the image data  
   sendCameraCmd(cameraGetData,16);  
   delay(10);  
   savePhoto(photoFile, tail);     
  }  
  photoFile.close();
  
  ledOff();  

  photoNumber ++;    
 }  
 void sendCameraCmd(char cmd[] ,int cmd_size)  
 {  
  for(char i = 0; i < cmd_size; i++)Serial.print(cmd[i]);   
 }

  
 ////////////////////////////////////////////////////////////////////////////  
 // led light  


 void ledOn()  
 {  
  digitalWrite(ledPin,HIGH);   
 }  


 void ledOff()  
 {  
  digitalWrite(ledPin,LOW);   
 }  


 void ledFlash()  
 {  
  digitalWrite(ledPin,HIGH);   
  delay(60);   
  digitalWrite(ledPin,LOW);   
  delay(60);   
  digitalWrite(ledPin,HIGH);   
  delay(60);   
  digitalWrite(ledPin,LOW);  
  delay(60);   
 }  

Processing:
 import processing.serial.*;  
 import controlP5.*;
  
 Serial dreadPort;  
 OutputStream output;  
 PImage img;

 ControlP5 controlP5;  
 Button photoButton;  
 PFont font = createFont("Arial", 14);
  
 String photoName;  
 String photoNameNumber;  
 int photoNumber = 1;  
 boolean cameraSave = false;  
 boolean photoOpen = false;  
 boolean photoSwitch = false;
  
 String filePath = "remoteCamera";  
 String fileStatus;  
 String folderName = timeStamp();

 int frameLocationX = 50;  
 int frameLocationY = 50;  
 int frameLocationSet = 0;
    
 String[] serialString;  
 String serialCheck;  
 String portName = "DreaduinoBluetooth";  
 int portNumber;  
 int serialIndex;
  
 int dataIn = 0;  
 int charC = 67;  
 int charA = 65;  
 int charM = 77;  
 int charP = 80;  
 int charI = 73;  
 int charE = 69;  
 int charO = 79;  
 int charF = 70;  
 int[] checkData = new int[3];  
 int[] checkEOF = new int[3]; 

 
 void setup()  
 {  
  size(640, 580);  
  background(40);   
  frame.setResizable(true);
  
  findSerialPort();
  
  dreadPort = new Serial(this, Serial.list()[portNumber], 115200);  
  dreadPort.clear();
  
  setupControlP5();
  
  statusUpdate(); 
 
  println("remoteCamera");  
  println("ready");  
 }  


 void draw()   
 {  
  if (frameLocationSet < 2) {  
   frame.setLocation(frameLocationX, frameLocationY);  
   frameLocationSet ++;  
  }   
  if (photoSwitch) remotePhoto();  
 } 

 
 void serialEvent(Serial myPort)   
 {   
  dataIn = myPort.read();
  
  checkData[2] = checkData[1];  
  checkData[1] = checkData[0];  
  checkData[0] = dataIn; 
 
  if (checkData[2] == charC && checkData[1] == charA && checkData[0] == charM) {    
   photoNameNumber = nf(photoNumber, 5);  
   photoName = ("img" + photoNameNumber + ".jpg");  
   filePath = (folderName + "/" + photoName);  
   println("saving");  
   cameraSave = true;  
   statusUpdate();  
  } 
 
  if (checkData[2] == charP && checkData[1] == charI && checkData[0] == charC) {    
   println("sending");  
   openPhoto();  
   return;  
  } 
 
  if (photoOpen) writePhoto();  
 }  


 void findSerialPort()  
 {  
  serialString = Serial.list(); 
 
  println(serialString);   

  for (int i = serialString.length - 1; i > 0; i--) {  
   serialCheck = serialString[i];  
   serialIndex = serialCheck.indexOf(portName);
  
   if (serialIndex > -1) portNumber = i;  
  }  
 }  


 void statusUpdate()   
 {  
  noStroke();  
  fill(40);   
  rect(0, 481, 640, 100);
  
  fill(255);  
  text(filePath, 300, 512);
   
  if (cameraSave) fileStatus = "saving";
  
  if (photoOpen) fileStatus = "sending"; 
 
  if (!cameraSave && !photoOpen) fileStatus = "ready";
   
  fill(255);  
  text(fileStatus, 300, 547);  
 }  


 void openPhoto()   
 {  
  if (!photoOpen) {  
   cameraSave = false;  
   photoOpen = true;  
   output = createOutput("rcamera/" + filePath);  
   statusUpdate();  
  }  
 }  


 void writePhoto()   
 {  
  try {  
   output.write(dataIn);  

   checkEOF[2] = checkEOF[1];  
   checkEOF[1] = checkEOF[0];  
   checkEOF[0] = dataIn;  

   if (checkEOF[2] == charE && checkEOF[1] == charO && checkEOF[0] == charF) {    
    closePhoto();  
    println(photoName + " received");  
    println("ready");  
    photoOpen = false;  
    statusUpdate();  
   }  
  }  
  catch (IOException e) {  
   e.printStackTrace();  
  }  
 }  

 
 void closePhoto()   
 {  
  try {  
   output.flush();  
   output.close();
  
   photoOpen = false; 
 
   statusUpdate();
  
   photoNumber ++;
  
   displayPhoto();  
  }   
  catch (IOException e) {  
   e.printStackTrace();  
  }  
 }  


 void displayPhoto()   
 {  
  noStroke();  
  fill(40);   
  rect(0, 0, 640, 480); 
 
  img = loadImage("rcamera/" + filePath);  
  image(img, 0, 0);  
 } 

 
 void setupControlP5()  
 {  
  controlP5 = new ControlP5(this);  
  controlP5.setControlFont(font); 
 
  photoButton = controlP5.addButton("photo", 0, 120, 500, 58, 58);  
  photoButton.setSwitch(photoSwitch);  
  photoButton.setColorBackground(color(150, 0, 0, 255));  
 } 

 
 void controlEvent(ControlEvent theEvent)  
 {  
  if (theEvent.controller().name()=="photo") remotePhoto();  
 }  


 void remotePhoto() {  
  dreadPort.write('P');
  
  photoSwitch = false;  
 }  


 void keyPressed() {  
  if (key == ' ' && !photoSwitch) {  
   
   photoSwitch = true; 
 
   remotePhoto();  
  }  
 } 


 String timeStamp() {  
  String timestamp = year()+"_"+month()+"_"+day()+"_"+hour()+"_"+minute();  
  return timestamp;  
 }   

5 comments:

  1. Have you tried using eye-fi wireless sd cards?

    ReplyDelete
    Replies
    1. This is the first I'm hearing of it. Looks interesting.

      Delete
  2. it's possible to save pic directly to computer?
    how to do it?
    tq

    ReplyDelete
    Replies
    1. I haven't tried, yet. The Arduino would need to send over Bluetooth any data that it receives from the camera. I imagine that the cameraReadData and savePhoto functions could be moved over to the Processing sketch without too much trouble. Use createOutput in Processing to save the data that was being saved to photoFile on the Arduino.

      Delete
  3. hey we are trying something similar... we use aubtm20 bluetooth breakout.. is it possible??
    n how do we save the image?

    ReplyDelete