Photo Spy Cam using Arduino

 

These shields designed for Arduino and Raspberry Pi integrates a SD Reader and Camera module which allows to easily take realtime photos and save them to a SD card.

The idea is simple: Take photo, download the photo from the Camera Shield (which is already compressed in JPEG) and save it to the SD Card. Then repeat this logic multiple times until the sd card is full. If the sd card is full, starts overwriting the first photos' files.

If you want to spy someone in your house and know what are they doing in your absense, this is one of the potential solutions for you...


Article Index

STEP 1: Introduction

Ingredients

spy_cam_kit_small

Spy Cam:

Difficulty: Medium medium

Preparation time: 1 day

STEP 2: Hardware selection

The boards (shields) we are going to use in this tutorial are the Linksprite LSY201 and VC0706 spy cams.

You can try to install other camera shields too, but be sure that the library that you use allows it... (We will see this point bellow)

It doesn't matter which SD Card reader/writer you use, as long it works.

 

STEP 3: System assembly

arduino_small

For the LSY201 camera shield, please connect the LSY201's pins to the following Arduino pins:

Here is an image of the LSY201 camera:



For the VC0706 camera shield, please connect the VC0706's pins to the following Arduino pins:

Here is an image of the VC0706 camera:



For the SD Card reader/writer shield, please connect the following pins:

Here is an image of the SD Card Reader/Writer Shield:

Installation

After you connect everything, just put everything in a box, like this:

installation_small

Then put the box in a hidden place...

STEP 4: Software: Arduino code

Remember, the shileds' pins have to be set on right Arduino pins, otherwise you must update my code.

When you switch on your arduino, it initializes the SD Card shield and the Camera and then starts taking photos and saving them...

Because of the SD Card partition type (FAT32), this logic saves the photos in different folders, where the maximum number of files per directory is 450, but you can change this, by changing the variables: 'maximum_files' and 'maximum_folders'.

Here is the code for the LSY201 camera shield:


/**
 * https://github.com/chendry/LSY201
 *
 * LSY201 - LinkSprte JPEG Colour Camera TTL Interface - Infrared - for Arduino:
 * - red: rx (IN) => it should be connected to the TX (OUT) of arduino => pin 3
 * - brown: tx (OUT) => it should be connected to the RX (IN) of arduino => pin 2
 * - purple: ground
 * - gray: 5v
 *
 * assuming the TX and RX pins on the camera are attached to pins 2 and 3 of the arduino.
 */
#define SERIAL_RX_BUFFER_SIZE 256
#define SERIAL_TX_BUFFER_SIZE 128

#include 
#include 
#include 
#include 

SoftwareSerial camera_serial(2, 3);
LSY201 camera;
uint16_t buffer_size = 240;
uint16_t file_num = 0;
uint16_t folder_num = 0;
uint16_t maximum_files = 450;//for 8GB card; 20 photos/minute; 34 days/8GB == 1.000.000 photos
uint16_t maximum_folders = 450;//1000 x 1000 = 1.000.000
char* tracking_fn = "TRACKING.TXT";

void setup()
{
  //unsigned long setupStartTime = micros();

  //activate 5V for the SD Card shield
  pinMode(8, OUTPUT);
  digitalWrite(8, HIGH);

  Serial.begin(38400);

  // Wait for the serial port to be opened
  while (!Serial) delay(25);

  Serial.println("Info: Setup is starting...");

  //check SD
  if (!SD.begin(9))
    Serial.println("Error: SD initialization failed,");
  //else
  //Serial.println("Info: SD Initialization complete,");
  delay(300);

  //Serial.println("Info: getting last pic num for the created files...");
  prepareNewSDFileAndFolderNum();
  //Serial.println(folder_num);
  //Serial.println(file_num);

  //Serial.println("Info: Creating folder if not exists...");
  createFolder();
  
  camera.setSerial(camera_serial);
  //camera.setDebugSerial(Serial);

  camera_serial.begin(38400);
  delay(50);

  //Serial.println("Info: Reseting cam to default values...");
  camera.reset();

  //Serial.println("Info: Changing picture size...");
  camera.setImageSize(LSY201::Medium);
  delay(2500);

  //Serial.println("Info: Reseting cam so image new size can take effect...");
  camera.reset();

  //Serial.println("Info: Setting image compression ratio...");
  //Call setCompressionRatio with a value between between 0 (low compression, high quality) and 0xFF (high compression, low quality.)
  camera.setCompressionRatio(0x60);//default is 0x20
  delay(3000);

  //Serial.println("Info: Clear cache");
  camera.stopTakingPictures();
  
  Serial.println("Info: Setup is DONE!");

  //unsigned long setupEndTime = micros();
  //Serial.print("Info: Setup elapsed time (in secs): ");
  //Serial.println((float)(setupEndTime - setupStartTime) / 1000000);
}

void loop()
{
  //unsigned long mainStartTime = micros();
  takePic();
  //unsigned long mainEndTime = micros();
  //Serial.print("Info: Main elapsed time (in secs): ");
  //Serial.println((float)(mainEndTime - mainStartTime) / 1000000);
}

void takePic()
{
  //Serial.println("Taking picture...");
  camera.takePicture();
  delay(50);

  //Serial.println("Saving picture...");
  savePicToFile();

  //Serial.println("Done.");

  camera.stopTakingPictures();
}

void savePicToFile() {
  //Serial.println("Info: Preparing folders");
  file_num++;
  
  if (file_num > maximum_files) {
    file_num = 1;
    folder_num++;

    if (folder_num > maximum_folders)
      folder_num = 1;
    
    createFolder();
  }
  
  //Serial.println("Info: Preparing new file name");
  char filename[20];
  sprintf(filename, "/DCIM/%i/%i.JPG", folder_num, file_num);

  //Serial.println("Info: saveLastFileName");
  //keep tracking of current photo num
  saveLastFileName(filename);

  // Create file
  //SD.remove(filename);
  //File testFile = SD.open(filename, FILE_WRITE);
  File testFile = SD.open(filename, (O_WRITE | O_CREAT | O_TRUNC));
  //if it opens ok
  if (testFile)
  {
    //save to file
    Serial.print("Info: Saving photo to SD: ");
    Serial.println(filename);

    uint8_t buf[buffer_size];
    uint16_t offset = 0;
    uint16_t i;
    
    //unsigned long startTime = micros();

    while (camera.readJpegFileContent(offset, buf, buffer_size))
    {
      for (i = 0; i < buffer_size; i ++) {
        testFile.write(buf[i]);
        //Serial.print(buf[i], HEX);
        //Serial.print(" ");
      }
      //Serial.println();

      offset += buffer_size;
    }

    //unsigned long endTime = micros();
    //Serial.print("Info: Elapsed time (in secs): ");
    //Serial.println((float)(endTime - startTime) / 1000000);

    Serial.print("Info: Bytes for photo: ");
    Serial.println(offset);
  }
  else //else it didn't open ok
    Serial.println("Error: Error opening file on SD,");

  //must close file to finish writing.
  testFile.close();
}

void saveLastFileName(char* filename) {
  File testFile = SD.open(tracking_fn, (O_WRITE | O_CREAT | O_TRUNC));
  testFile.write(filename);
  testFile.close();
}

void createFolder() {
  char folder[15];
  sprintf(folder, "/DCIM/%i/", folder_num);
        
  if (!SD.exists(folder)) {
    SD.mkdir(folder);
  }
}

void prepareNewSDFileAndFolderNum() {
  prepareNewSDFileAndFolderNumFromTrackingFile();
  
  File root;
  
  if (folder_num == 0) {
    root = SD.open("/DCIM/");
    folder_num = getFileLastNumForDirectory(root);
    root.close();
    folder_num = folder_num > 0 ? folder_num : 1;
  }
  
  if (file_num == 0) {
    char folder[15];
    sprintf(folder, "/DCIM/%i/", folder_num);
    
    if (SD.exists(folder)) {
      root = SD.open(folder);
      file_num = getFileLastNumForDirectory(root);
      root.close();
    }
  }

  //Serial.print("folder_num:");
  //Serial.println(folder_num);
  //Serial.print("file_num:");
  //Serial.println(file_num);
  //while(1);
}

void prepareNewSDFileAndFolderNumFromTrackingFile() {
  folder_num = 0;
  file_num = 0;
  
  if (SD.exists(tracking_fn)) {
    File f = SD.open(tracking_fn, FILE_READ);
    String fname = "";
    while (f.available()) {
      char ltr = f.read();
      fname += ltr;
    }
    f.close();

     if (fname.length() > 1) {
      if (fname.substring(0, 1) == "/")
        fname = fname.substring(1);

      if (fname.substring(0, 5) == "DCIM/")
        fname = fname.substring(5);
      
      uint8_t pos = fname.indexOf("/");

      if (pos > 0) {
        folder_num = getFileNameNumber( fname.substring(0, pos) );
        file_num = getFileNameNumber( fname.substring(pos + 1) );
      }
    }
  }
}

uint16_t getFileLastNumForDirectory(File dir) {
  uint16_t n = 0;
  uint16_t fn = 0;
  File entry;
  String name;

  dir.rewindDirectory();
  
  while (true) {
    entry =  dir.openNextFile();
    
    if (!entry)  // no more files
      break;

    name = (String) entry.name();
    entry.close();
    
    fn = getFileNameNumber(name);
    
    if (fn > n) {
      n = fn;
    }
  }

  return n;
}

uint16_t getFileNameNumber(String name) {
  name.toUpperCase();
  name.replace(".JPG", "");

  if (isValidNumber(name))
    return name.toInt();
  return 0;
}

boolean isValidNumber(String str) {
  uint8_t l = str ? str.length() : 0;
  
  if (l > 0) {
    for (byte i = 0; i < l; i++)
    {
      if (!isDigit(str.charAt(i))) 
        return false;
    }
    return true;
  }
  return false;
}

Note that this code uses the LSY201 library and you can download it here or here.

Please click here to download this code for the LSY201.


Here is the code for the VC0706 and other camera shields:


/**
 * LSY201 - LinkSprte JPEG Colour Camera TTL Interface - Infrared - for Arduino:
 * - red: rx (IN) => it should be connected to the TX (OUT) of arduino => pin 3
 * - brown: tx (OUT) => it should be connected to the RX (IN) of arduino => pin 2
 * - purple: ground
 * - gray: 5v
 *
 * VC0706 3.3V Camera Module TTL/UART Jpeg/CVBS For AVR STM32 Arduino Compatible:
 * - It's writen in the board, so if you use the same cable that you use for the LinkSprite Camera above:
 *  + cvbs: N/A for this example
 *  + 3.3V: red cable
 *  + TX (OUT): brown cable => it should be connected to the RX (IN) of arduino => pin 2
 *  + RX (IN): purple cable => it should be connected to the TX (OUT) of arduino => pin 3
 *  + GND: gray cable
 */

#include 
#include 
#include 
#include 

File testFile;
SoftwareSerial s(2, 3);
JPEGCamera cam(s);
char filename[15];
int num = 0;
int numOfBytes;

int camType = 1; //1 for LSY201 cam; 2 for VC0706 cam.
int photoSize = 1; // 0 -> 160x120 / 1 -> 320x240 / 2-> 640x480

unsigned long mainStartTime, mainEndTime, startTime, endTime;

void setup()
{
  //activate 5V for the SD Card shield
  pinMode(8, OUTPUT);
  digitalWrite(8, HIGH);

  Serial.begin(38400);

  // Wait for the serial port to be opened
  while (!Serial) delay(25);

  //check SD
  if (!SD.begin(9))
    Serial.println("Error: SD initialization failed,");
  else
    Serial.println("Info: SD Initialization complete,");

  // Serial port connected to the cam
  if (camType == 1) {
    s.begin(38400);//for LSY201 cam
    delay(50);
  
    Serial.println("Info: Reseting cam...");
    //reset cam and wait.
    cam.reset();
    delay(3000);
  }
  else {
    s.begin(115200);//for VC0706 cam
    delay(50);

    Serial.println("Info: Reseting cam...");
    //reset cam and wait.
    cam.reset();
    delay(3000);

    Serial.println("Info: Changing baud rate...");
    //change baud rate to work at 3.3v
    cam.chBaudRate(2);// 0 -> 9600 / 1 -> 19200 / 2 ->  38400 / 3 -> 57600 / 4 -> 115200
    delay(50);
  
    //change serial to match new camera baud rate
    s.end();
    s.begin(38400);
    delay(50);
  }

  //Serial.println("Info: de-activate powersaving..."); //no need. Already de-activated by default
  //cam.powerSaving(false);
  //delay(50);

  Serial.println("Info: Changing picture size...");
  //change picture size
  cam.chPictureSize(photoSize);
  delay(2500);

  Serial.println("Info: getting last pic num for the created files...");
  num = getNewSDFileNum();

  Serial.println("Info: Setup is DONE!");
}

void loop()
{
  mainStartTime = millis();
  takePic();
  mainEndTime = millis();
  Serial.print("Info: Main elapsed time (in secs): ");
  Serial.println((mainEndTime - mainStartTime) / 1000);
}

void takePic()
{
  Serial.println("Info: Preparing to take new photo.");

  //must call stopPictures before a new photo is taken.
  cam.stopPictures();
  delay(50);

  //take photo
  cam.takePicture();
  delay(50);

  num++;
  sprintf(filename, "PIC_%i.JPG", num);

  if (SD.exists(filename)) {
    SD.remove(filename);
  }

  // Create file
  testFile = SD.open(filename, FILE_WRITE);
  //if it opens ok
  if (testFile)
  {
    //save to file
    Serial.print("Info: Saving photo to SD: ");
    Serial.println(filename);
    
    startTime = millis();
    numOfBytes = cam.readData(testFile);
    endTime = millis();

    Serial.print("Info: Elapsed time (in secs): ");
    Serial.println((endTime - startTime) / 1000);

    Serial.print("Info: Bytes for photo: ");
    Serial.println(numOfBytes);
  }
  else //else it didn't open ok
    Serial.println("Error: Error opening file on SD,");

  //must close file to finish writing.
  testFile.close();
}

int getNewSDFileNum() {
  File root = SD.open("/");

  int n = getFileLastNumForDirectory(root);

  root.close();

  return n > 0 ? n : 0;
}

int getFileLastNumForDirectory(File dir) {
  int n = 0;
  int fn = 0;

  while (true) {
    File entry =  dir.openNextFile();

    if (!entry) {
      // no more files
      break;
    }

    fn = 0;

    if (entry.isDirectory()) {
      //fn = prepareFileLastNumForDirectory(entry);//no need to be recursively
    }
    else {
      String name = String(entry.name());

      if (name.startsWith("PIC_") && name.endsWith(".JPG")) {
        int pos = name.indexOf(".JPG");
        name = name.substring(4, pos);

        if (isValidNumber(name))
          fn = name.toInt();
      }
    }

    if (fn > n) {
      n = fn;
    }

    entry.close();
  }

  return n;
}

boolean isValidNumber(String str) {
  if (str && str.length() > 0) {
    for (byte i = 0; i < str.length(); i++)
    {
      if (!isDigit(str.charAt(i))) return false;
    }
    return true;
  }
  return false;
}

Note that this code uses the JPEGCamera library and you can download it here.

Please click here to download this code for VC0706 and other camera shields.

STEP 5: Get the kit

Cam and SD Card Shields for Arduino:

STEP 6: Share this and your project with the world

Share this and your project with the world, this is, create a web-page and index it on the search engines, create a Facebook page, share this page link in your facebook and twitter, etc...
By sharing your project, you are giving to other people the opportunity to develop new projects and improve yours...

STEP 7: Issues

Currently I'm having only 1 issue, where the SD Card shield stops saving photos correctly. Basically it saves correctly around 55.000 photos (around 750MB) and after that it starts saving corrupted files with 0 bytes of size.

Maybe the problem is my SD Card shield, since it only cost me a few cents, but I'll update this page again, as soon as I fixed this issue.

 
Joao Pinto - 10/12/2014