lunes, 28 de noviembre de 2011

Cámara sobre servidor web con envío de tweet y detección de movimiento

El objetivo de este proyecto es utilizar una pequeña cámara que cuando detecta movimiento tome una foto, la almacene en una tarjeta de memoria SD colocada en el ethernet shield, envíe un tweet de aviso y mediante un servidor web (Tiny web server) incorporado en el propio arduino permita que el usuario desde internet se conecte y pueda ver la lista de todas las fotografías almacenadas en la tarjeta y visualizar cualquiera de ellas. También permitirá que el usuario active o desactive las funciones de envío de tweets y la de detección automática de movimiento.
Cuando se detecta moviento antes de hacer la foto se cierra un relé al cual se podría conectar una lámpara de modo que haya luz cuando se haga la foto.


// He tenido que reducir al mínimo las cadenas de texto utilizadas pues estoy en el límite de la memoria del Arduino UNO
// Para que desde el navegador nos podamos descargar un fichero que ya esta en la tarjeta simplemente
// pondremos la ip del sitio seguido de /nombrearchivo Por ejemplo: http://192.168.1.177/IMAGE10.JPG
// Si Unicamente ponemos la ip sin nada detras nos mostrara la lista de los ficheros que contiene la tarjeta
// http://192.168.1.177/1 Activaría la detección de movimiento
// http://192.168.1.177/2 Desactivaría la detección de movimiento
// http://192.168.1.177/3 Activaría el envío de tweets
// http://192.168.1.177/4 Desactivaría el envío de tweets
//
// Version V2: Le pongo activar o desactivar Motion añadiendo en la URL motion_off o motion_on
// Version V3: He quitado muchos if, quito upload file, activar o desactivar motio y lo reduzco todo al mínimo para poder
// mandar el tweet y que quepa en la memoria
// Version V4: Pruebo a almacenar cosas en SRAM en vez de en Flash. Pongo activar o desactivar envio de tweet poniendo
// en la URL tweet_on o tweet_off. Cuando se envia un tweet se pone a off automaticamene. Cada vez que se manda un comando
// se muestra la pagina index con el estado de los switches y la lista de ficheros.
// Version V5: He conectado el rele que permanecerá un intervalo de tiempo encendido desde que se enciende y así poder conectar
// una luz para que la cámara pueda sacar una foto nítida
// IMPORTANTE: He intentado poner todo el proyecto sobre Arduino Mega 2560 para terminar con los problemas de memoria
// pero no ha sido posible porque la versión 10 de
// la librería NewSoftSerial no funciona sobre Mega. He probado con la beta 11 de
// http://arduiniana.org/2011/01/newsoftserial-11-beta pero no he conseguido que funcione
// TODO: crear una cuenta de twitter especifica y habilitarla para envio de sms
// TODO: abrir puertos del PC para ver el Arduino
// TODO: posibilidad de borrar las fotografías.
// TODO: añadir opción para que pueda dispararse de manera remota una foto aunque no se haya detectado movimiento
// TODO: poner la cámara sobre unos servos de modo que puediera moverse su orientación de manera remota

//
// Cuando graba 99 fotos la nueva siempre se graba como 99, una y otra vez. Decido dejarlo así.
//
// Sobre Tiny Web Server http://www.webweavertech.com/ovidiu/weblog/archives/000484.html
// Para el envío de tweets, como es muy costoso en términos de memoria se utiliza una aplicación externa http://arduino-tweet.appspot.com
// Ethernet shield http://www.adafruit.com/products/201


Ejemplo de la pantalla con el índice de todas las fotos:


Código fuente CamaraTinyWebServerV5:

// -*- c++ -*-
// Author: Antonio García Figueras
// Date: Noviembre 2011
// Programa para controlar una microcámara que al detectar moviento active un relé, al que se puede conectar una bombilla,
// tome una foto y la guarde en la tarjeta de memoria SD que se colocaría en el ethernet shield. Seguidamente se enviaría un tweet.
// Foto con los circuitos necesarios en
// http://xp-dev.com/svn/arduino_antonio/trunk/C%C3%A1mara/Foto%20conexi%C3%B3n%20c%C3%A1mara%20a%20shiel%20ethernet%20y%20relay.jpg
// Este ethernet shield permitirá
// que el servidor web (Tiny web server) que he usado permita visualizar la lista de fotografías que se hayan ido almacenando en la tarjeta
// de memoria y que el usuario desde internet pueda visualizar cualquiera de ellas. También puede activar o desactiva la
// función de detectar movimiento de la cámara y el envío o no de tweets.
// He tenido que reducir al mínimo las cadenas de texto utilizadas pues estoy en el límite de la memoria del Arduino UNO
// Para que desde el navegador nos podamos descargar un fichero que ya esta en la tarjeta simplemente
// pondremos la ip del sitio seguido de /nombrearchivo Por ejemplo: http://192.168.1.177/IMAGE10.JPG
// Si Unicamente ponemos la ip sin nada detras nos mostrara la lista de los ficheros que contiene la tarjeta
// http://192.168.1.177/1 Activaría la detección de movimiento
// http://192.168.1.177/2 Desactivaría la detección de movimiento
// http://192.168.1.177/3 Activaría el envío de tweets
// http://192.168.1.177/4 Desactivaría el envío de tweets
//
// Version V2: Le pongo activar o desactivar Motion añadiendo en la URL motion_off o motion_on
// Version V3: He quitado muchos if, quito upload file, activar o desactivar motio y lo reduzco todo al mínimo para poder
// mandar el tweet y que quepa en la memoria
// Version V4: Pruebo a almacenar cosas en SRAM en vez de en Flash. Pongo activar o desactivar envio de tweet poniendo
// en la URL tweet_on o tweet_off. Cuando se envia un tweet se pone a off automaticamene. Cada vez que se manda un comando
// se muestra la pagina index con el estado de los switches y la lista de ficheros.
// Version V5: He conectado el rele que permanecerá un intervalo de tiempo encendido desde que se enciende y así poder conectar
// una luz para que la cámara pueda sacar una foto nítida
// IMPORTANTE: He intentado poner todo el proyecto sobre Arduino Mega 2560 para terminar con los problemas de memoria
// pero no ha sido posible porque la versión 10 de
// la librería NewSoftSerial no funciona sobre Mega. He probado con la beta 11 de
// http://arduiniana.org/2011/01/newsoftserial-11-beta pero no he conseguido que funcione
// TODO: crear una cuenta de twitter especifica y habilitarla para envio de sms
// TODO: abrir puertos del PC para ver el Arduino
// TODO: posibilidad de borrar las fotografías.
// TODO: añadir opción para que pueda dispararse de manera remota una foto aunque no se haya detectado movimiento
// TODO: poner la cámara sobre unos servos de modo que puediera moverse su orientación de manera remota

//
// Cuando graba 99 fotos la nueva siempre se graba como 99, una y otra vez. Decido dejarlo así.
//
// Sobre Tiny Web Server http://www.webweavertech.com/ovidiu/weblog/archives/000484.html
// Para el envío de tweets, como es muy costoso en términos de memoria se utiliza una aplicación externa http://arduino-tweet.appspot.com
// Ethernet shield http://www.adafruit.com/products/201

// Para el Tiny Web Server
#include <SPI.h>
#include <Ethernet.h>
#include <Flash.h>
#include <SdFat.h>
#include <SdFatUtil.h>
#include <TinyWebServer.h>

// Para la camara
#include <VC0706.h>
#include <NewSoftSerial.h>

// Para Tweeter
#include <EthernetDNS.h>
#include <Twitter.h>

boolean file_handler(TinyWebServer& web_server);
boolean index_handler(TinyWebServer& web_server);

boolean tweetOn=false;

boolean isRelayOn=false;

long previousMillis = 0;
//Intervalo en milisegundos que permanecerá el relé encendido
long interval = 10000;


// change this to match your SD shield or module;
// Arduino Ethernet shield: pin 4
// Adafruit SD shields and modules: pin 10
// Sparkfun SD shield: pin 8
#define chipSelect 4
// This is the camera pin connection. Connect the camera TX
// to pin 2, camera RX to pin 3
NewSoftSerial cameraconnection = NewSoftSerial(2, 3);
// pass the serial connection to the camera object
VC0706 cam = VC0706(&cameraconnection);

TinyWebServer::PathHandler handlers[] = {
  // Work around Arduino's IDE preprocessor bug in handling /* inside
  // strings.
  //
  // `put_handler' is defined in TinyWebServer
   {"/1", TinyWebServer::GET, &motionOn_handler },
  {"/2", TinyWebServer::GET, &motionOff_handler },
  {"/3", TinyWebServer::GET, &tweetOn_handler },
  {"/4", TinyWebServer::GET, &tweetOff_handler },
  {"/", TinyWebServer::GET, &index_handler },
//  {"/upload/" "*", TinyWebServer::PUT, &TinyWebPutHandler::put_handler },
  {"/" "*", TinyWebServer::GET, &file_handler },
  {NULL},
};

const char* headers[] = {
//  "Content-Length",
//  NULL
};

TinyWebServer web = TinyWebServer(handlers, headers);

//boolean has_filesystem = true;
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

static uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

// Don't forget to modify the IP to an available one on your home network
byte ip[] = { 192, 168, 1, 177 };

// Your Token to Tweet (get it from http://arduino-tweet.appspot.com/)
Twitter twitter("353548191-qy3gHOQDgwxiKVW8bsRihE4rRTllZIkC4CFEdGDD");

void send_file_name(TinyWebServer& web_server, const char* filename) {
  if (!filename) {
    web_server.send_error_code(404);
//    web_server << F("Could not parse URL");
  } else {
    TinyWebServer::MimeType mime_type
      = TinyWebServer::get_mime_type_from_filename(filename);
    web_server.send_error_code(200);
    web_server.send_content_type(mime_type);
    web_server.end_headers();
    if (file.open(&root, filename, O_READ)) {
//      Serial << F("Read file "); Serial.println(filename);
      web_server.send_file(file);
      file.close();
    } else {
//      web_server << F("Could not find file: ") << filename << "\n";
    }
  }
}

boolean file_handler(TinyWebServer& web_server) {
  char* filename = TinyWebServer::get_file_from_path(web_server.get_path());
  send_file_name(web_server, filename);
  free(filename);
  return true;
}


boolean tweetOn_handler(TinyWebServer& web_server){
  tweetOn=true;
  generaIndex(web_server);
  return true;
}

boolean tweetOff_handler(TinyWebServer& web_server){
  tweetOn=false;
  generaIndex(web_server); 
  return true;
}

boolean motionOff_handler(TinyWebServer& web_server) {
//  Serial.println("Recibida peticion de motion OFF");
  cam.setMotionDetect(false);
  generaIndex(web_server); 
  return true;
}
//
boolean motionOn_handler(TinyWebServer& web_server) {
//  Serial.println("Recibida peticion de motion ON");
  cam.setMotionDetect(true);
  generaIndex(web_server); 
  return true;
}

boolean index_handler(TinyWebServer& web_server) {
//  send_file_name(web_server, "INDEX.HTM");
//  web_server.send_error_code(200);
//  web_server.end_headers();
////  web_server << F("<html><body><h1>Hola. La tarjeta contiene los siguientes ficheros:</h1></body></html>\n");
//  web_server << "<html><body>motion:";
//  if (cam.getMotionDetect())
//    web_server << "ON";
//  else
//    web_server << "OFF";
//   
//  web_server << "<br>tweet:";
//    if (tweetOn)
//    web_server << "ON";
//  else
//    web_server << "OFF";
//   
//  ListFiles(web_server, LS_SIZE);
//  web_server << "</body></html>\n"; 

  generaIndex(web_server);
  return true;
}

void generaIndex(TinyWebServer& web_server){
  web_server.send_error_code(200);
  web_server.end_headers();
//  web_server << F("<html><body><h1>Hola. La tarjeta contiene los siguientes ficheros:</h1></body></html>\n");
  web_server << "<html><body>mot:";
  if (cam.getMotionDetect())
    web_server << "ON";
  else
    web_server << "OFF";
   
  web_server << "<br>tw:";
    if (tweetOn)
    web_server << "ON";
  else
    web_server << "OFF";
   
  ListFiles(web_server, LS_SIZE);
  web_server << "</body></html>";  
}

void ListFiles(TinyWebServer& web_server, uint8_t flags) {
  // This code is just copied from SdFile.cpp in the SDFat library
  // and tweaked to print to the client output in html!
  dir_t p;
 
  root.rewind();
  web_server << "<ul>";
  while (root.readDir(p) > 0) {
    // done if past last used entry
    if (p.name[0] == DIR_NAME_FREE) break;

    // skip deleted entry and entries for . and ..
    if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') continue;

    // only list subdirectories and files
    if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;



    // print any indent spaces
    web_server << "<li><a href=\"";
    for (uint8_t i = 0; i < 11; i++) {
      if (p.name[i] == ' ') continue;
      if (i == 8) {
        web_server << ".";
      }
      web_server << p.name[i];
    }
    web_server << "\">";
   
    // print file name with possible blank fill
    for (uint8_t i = 0; i < 11; i++) {
      if (p.name[i] == ' ') continue;
      if (i == 8) {
        web_server << ".";
      }
      web_server << p.name[i];
    }
   
    web_server << "</a>";
   
    if (DIR_IS_SUBDIR(&p)) {
      web_server << "/";
    }

    // print modify date/time if requested
    if (flags & LS_DATE) {
       root.printFatDate(p.lastWriteDate);
       web_server << " ";
       root.printFatTime(p.lastWriteTime);
    }
    // print size if requested
    if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) {
      web_server << " ";
      web_server << p.fileSize;
    }
    web_server << "</li>";
  }
  web_server << "</ul>";
}

//void file_uploader_handler(TinyWebServer& web_server,
//               TinyWebPutHandler::PutAction action,
//               char* buffer, int size) {
//  static uint32_t start_time;
//  static uint32_t total_size;
//
//  switch (action) {
//  case TinyWebPutHandler::START:
//    start_time = millis();
//    total_size = 0;
//    if (!file.isOpen()) {
//      // File is not opened, create it. First obtain the desired name
//      // from the request path.
//      char* fname = web_server.get_file_from_path(web_server.get_path());
//      if (fname) {
////    Serial << F("Creating ") << fname << "\n";
//    file.open(&root, fname, O_CREAT | O_WRITE | O_TRUNC);
//    free(fname);
//      }
//    }
//    break;
//
//  case TinyWebPutHandler::WRITE:
//    if (file.isOpen()) {
//      file.write(buffer, size);
//      total_size += size;
//    }
//    break;
//
//  case TinyWebPutHandler::END:
//    file.sync();
////    Serial << F("Wrote ") << file.fileSize() << F(" bytes in ")
////       << millis() - start_time << F(" millis (received ")
////           << total_size << F(" bytes)\n");
//    file.close();
//  }
//}

void inicializaCamara(){
//  Serial.println("VC0706 Camera test");
   
  // Try to locate the camera
  cam.begin();
//  if (cam.begin()) {
////    Serial.println("Camera Found:");
//  } else {
////    Serial.println("No camera found?");
//    return;
//  }
  // Print out the camera version information (Realmente no es optional)
  char *reply = cam.getVersion();
//  if (reply == 0) {
////    Serial.print("Failed to get version");
//  } else {
////    Serial.println("-----------------");
////    Serial.print(reply);
////    Serial.println("-----------------");
//  }

  // Set the picture size - you can choose one of 640x480, 320x240 or 160x120
  // Remember that bigger pictures take longer to transmit!
 
//  cam.setImageSize(VC0706_640x480);        // biggest
//  cam.setImageSize(VC0706_320x240);        // medium
  cam.setImageSize(VC0706_160x120);          // small

  // You can read the size back from the camera (optional, but maybe useful?)
//  uint8_t imgsize = cam.getImageSize();
//  Serial.print("Image size: ");
//  if (imgsize == VC0706_640x480) Serial.println("640x480");
//  if (imgsize == VC0706_320x240) Serial.println("320x240");
//  if (imgsize == VC0706_160x120) Serial.println("160x120");


  //  Motion detection system can alert you when the camera 'sees' motion!
  cam.setMotionDetect(true);           // turn it on
  //cam.setMotionDetect(false);        // turn it off   (default)

  // You can also verify whether motion detection is active!
//  Serial.print("Motion detection is ");
//  if (cam.getMotionDetect())
////    Serial.println("ON");
//  else
//    Serial.println("OFF");
}

void tomaFoto(){
//   Serial.println("Motion!");  
   cam.setMotionDetect(false);
  
   cam.takePicture();
//  if (! cam.takePicture())
////    Serial.println("Failed to snap!");
//  else
//    Serial.println("Picture taken!");
 
  char filename[13];
  strcpy(filename, "IMAGE00.JPG");
  
  for (int i = 0; i < 100; i++) {
    filename[5] = '0' + i/10;
    filename[6] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! root.exists(filename)) {
      break;
    }
  }
 
  SdFile imgFile;
  imgFile.open(&root, filename, O_CREAT | O_WRITE | O_TRUNC);
 
  uint16_t jpglen = cam.frameLength();
//  Serial.print(jpglen, DEC);
//  Serial.println(" byte image");
//
//  Serial.print("Writing image to "); Serial.print(filename);
 
  while (jpglen != 0) {
    // read 64 bytes at a time;
    // cambio a 32 bytes el buffer para que funciones
    uint8_t *buffer;
    uint8_t bytesToRead = min(32, jpglen);
    buffer = cam.readPicture(bytesToRead);
    imgFile.write(buffer, bytesToRead);

    //Serial.print("Read ");  Serial.print(bytesToRead, DEC); Serial.println(" bytes");

    jpglen -= bytesToRead;
  }
  imgFile.close();
//  Serial.println("...Done!");
  cam.resumeVideo();
  cam.setMotionDetect(true);
}

void enviaTweet(){
  if (twitter.post("B")){
    twitter.wait();
  }
}

void relayOn(){
    digitalWrite(6, HIGH);
    isRelayOn=true;
    previousMillis = millis();
}

void relayOff(){
    digitalWrite(6, LOW);
    isRelayOn=false;   
}
 

void setup() {
//  Serial.begin(9600);
//  Serial << F("Free RAM: ") << FreeRam() << "\n";

  // initialize the SD card
//  Serial << F("Setting up SD card...\n");
  pinMode(10, OUTPUT); // set the SS pin as an output (necessary!)
  digitalWrite(10, HIGH); // but turn off the W5100 chip!
  card.init(SPI_FULL_SPEED, 4);
//  if (!card.init(SPI_FULL_SPEED, 4)) {
////    Serial << F("card failed\n");
//    has_filesystem = false;
//  }
  // initialize a FAT volume
  volume.init(&card);
//  if (!volume.init(&card)) {
////    Serial << F("vol.init failed!\n");
//    has_filesystem = false;
//  }

  root.openRoot(&volume);
//  if (!root.openRoot(&volume)) {
////    Serial << F("openRoot failed");
//    has_filesystem = false;
//  }

//  if (has_filesystem) {
//    // Assign our function to `upload_handler_fn'.
//    TinyWebPutHandler::put_handler_fn = file_uploader_handler;
//  }

//  Serial << F("Setting up the Ethernet card...\n");
  Ethernet.begin(mac, ip);

  // Start the web server.
//  Serial << F("Web server starting...\n");
  web.begin();

//  Serial << F("Ready to accept HTTP requests.\n");
 
  inicializaCamara();
    pinMode(6, OUTPUT);
}

void loop() {

//  if (has_filesystem) {
    web.process();
//  }
 
  if (cam.motionDetected()) {
    tomaFoto();
    relayOn();
    if (tweetOn){
      enviaTweet();
      // En cuanto envio un tweet desactivo el envío
      tweetOn=false;
    }
  }
 
  if(isRelayOn && (millis()-previousMillis > interval)) {
    //Apagamos el relay si ya se ha sobrepasado encedido el intervalo estipulado
    relayOff();
  }
}