Adafruit_EyeFi.ino
  1  // SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  /*
  6  This Arduino sketch combines a serial JPEG camera, data logging shield
  7  and an Eye-Fi wireless SD card in order to provide remote monitoring.
  8  SEE NOTES LATER IN CODE REGARDING "X2" CARDS - MUST EXPLICITLY ENABLE.
  9  After initializing the hardware, the sketch sets the camera to motion-
 10  detect mode and then runs in a loop monitoring for changes.  When
 11  movement is sensed, an image is captured and written to the SD card,
 12  at which point (if the Eye-Fi card is properly configured and a Wi-Fi
 13  internet connection is available), this will be transferred to the
 14  Eye-Fi service -- and in turn to your computer, smartphone, etc.
 15  IMPORTANT DISCLAIMERS: this is a plaything -- do not rely on this for
 16  actual security.  Also, users should observe local privacy ordinances
 17  regarding surveillance.
 18  Written by Adafruit Industries.  Public domain.
 19  Resources:
 20  Adafruit Data logging shield for Arduino:
 21  https://www.adafruit.com/products/243
 22  TTL Serial JPEG Camera with NTSC Video:
 23  https://www.adafruit.com/products/397
 24   -or-
 25  Weatherproof TTL Serial JPEG Camera with NTSC Video and IR LEDs:
 26  https://www.adafruit.com/products/613
 27  This also requires an Arduino (or Arduino-compatible) board such as
 28  the Arduino Uno or Duemilanove (*NOT COMPATIBLE WITH MEGA*) and a
 29  suitable power supply.  Power uptions include:
 30  9 VDC 1000mA regulated switching power adapter - UL listed:
 31  https://www.adafruit.com/products/63
 32   -or-
 33  6 x AA battery holder with 5.5mm/2.1mm plug:
 34  https://www.adafruit.com/products/248
 35   -or-
 36  MintyBoost kit:
 37  http://www.adafruit.com/products/14
 38  */
 39  
 40  // Although this sketch uses the hardware serial UART, it's still necessary
 41  // to #include SoftwareSerial.h/NewSoftSerial.h here.  This is because the
 42  // VC0706 camera library includes support for both "hard" and "soft" serial,
 43  // but cannot #include the right header on its own without some help.  Why?
 44  // From http://arduino.cc/en/Hacking/BuildProcess :
 45  // "The include path includes the sketch's directory [...] the avr include
 46  // directory (<ARDUINO>/hardware/tools/avr/avr/include/), as well as any
 47  // library directories (in <ARDUINO>/hardware/libraries/) which contain a
 48  // header file WHICH IS INCLUDED BY THE MAIN SKETCH FILE." (emphasis mine)
 49  // So including it here makes the VC0706 library happy:
 50  #if ARDUINO >= 100
 51   #include <SoftwareSerial.h>
 52  #else
 53   #include <NewSoftSerial.h>
 54  #endif
 55  #include <Adafruit_VC0706.h> // Serial JPEG camera library
 56  #include <SD.h>              // SD card library
 57  #include <RTClib.h>          // Realtime clock library
 58  #include <Wire.h>            // Also needed for RTC
 59  
 60  // This sketch uses the hardware serial UART (on pins 0 & 1) for more
 61  // robust communication with the camera.  Unfortunately this means we
 62  // can't use the USB port to issue debugging info to a computer...
 63  // instead, status is conveyed through the green and red LEDs on the
 64  // Data Logging Shield, connected to the following pins:
 65  #define RED_LED   5
 66  #define GREEN_LED 6
 67  
 68  // Green LED does a "sleep throb" when camera is idle.  This table
 69  // contains the brightness levels over time (reverse for second half).
 70  const PROGMEM byte sleepTab[] = {
 71        0,   0,   0,   0,   0,   0,   0,   0,   0,   1,
 72        1,   1,   2,   3,   4,   5,   6,   8,  10,  13,
 73       15,  19,  22,  26,  31,  36,  41,  47,  54,  61,
 74       68,  76,  84,  92, 101, 110, 120, 129, 139, 148,
 75      158, 167, 177, 186, 194, 203, 211, 218, 225, 232,
 76      237, 242, 246, 250, 252, 254, 255 };
 77  
 78  // SD card chip select line varies among boards/shields:
 79  // Adafruit SD shields and modules: pin 10
 80  // Arduino Ethernet shield: pin 4
 81  // Sparkfun SD shield: pin 8
 82  #define chipSel 10
 83  
 84  RTC_DS1307      clock;
 85  Adafruit_VC0706 cam = Adafruit_VC0706(&Serial);
 86  char            directory[] = "DCIM/CANON999", // Emulate Canon folder layout
 87                  filename[]  = "DCIM/CANON999/IMG_0000.JPG";
 88  byte            sleepPos; // Current "throb" table position
 89  int             imgNum      = 0;
 90  const int       minFileSize = 20 * 1024; // Eye-Fi requires minimum file size
 91  
 92  // -------------------------------------------------------------------------
 93  
 94  void setup() {
 95  
 96    pinMode(RED_LED  , OUTPUT);
 97    pinMode(GREEN_LED, OUTPUT);
 98    digitalWrite(RED_LED  , LOW);
 99    digitalWrite(GREEN_LED, HIGH); // "Hello!" indicator
100  
101    // All the example sketches for the serial JPEG camera use pins 2 & 3
102    // (and SoftwareSerial/NewSoftSerial).  But this sketch uses the hardware
103    // UART on pins 0 & 1 for robustness.  If you've permanently wired the
104    // cam to pins 2 & 3, this is perfectly fine, you can just use short
105    // jumpers between 0 & 2 and 1 & 3 (remove when uploading code).  But
106    // make sure those pins are never used as outputs for any features you
107    // might add to this sketch!
108    pinMode(2,INPUT); pinMode(3,INPUT);
109  
110    Wire.begin();  // IMPORTANT: the clock should have previously been set
111    clock.begin(); // using the 'ds1307' example sketch included with RTClib.
112    SdFile::dateTimeCallback(dateTime); // Register timestamp callback
113    if(!clock.isrunning()) error(75);   // Init clock; error = hyper flash
114    if(!SD.begin(chipSel)) error(250);  // Init SD card; error = quick flash
115    if(!cam.begin())       error(1000); // Init camera; error = slow flash
116  
117    // Un-comment this line if using an Eye-Fi X2 card:
118    // SD.enableCRC(true);
119  
120    // Create image directory if not already present; error = solid red
121    if(!SD.exists(directory) && !SD.mkdir(directory)) error(1);
122  
123    delay(1000); // Need to pause a moment before camera accepts commands
124    cam.setImageSize(VC0706_640x480); // Let's use the largest image size
125  
126    // Set up LED "sleep throb" using Timer1 interrupt:
127    TCCR1A = _BV(WGM11); // Mode 14 (fast PWM), 64:1 prescale, OC1A off
128    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11) | _BV(CS10);
129    ICR1   = 8333;       // ~30 Hz between sleep throb updates
130    sei();                // Enable global interrupts
131    // Timer1 interrupt is enabled when loop() starts
132  }
133  
134  
135  void loop() {
136  
137    sleepPos = sizeof(sleepTab);   // Start up LED "sleep throb" at top
138    TIMSK1  |= _BV(TOIE1);         // Enable Timer1 interrupt for throb
139    nextFilename();                // Scan directory for next name
140    cam.resumeVideo();             // Enable composite live output
141    cam.setMotionDetect(true);     // Enable motion detection
142    while(!cam.motionDetected());  // Wait for motion trigger ... ... ...
143  
144    // Motion detected!
145    cam.setMotionDetect(false);    // Turn it off while we work 
146    TIMSK1 &= ~_BV(TOIE1);         // And stop the pulsing LED
147    digitalWrite(GREEN_LED, HIGH); // Just show solid green
148  
149    delay(500); // Pause half a sec between motion sense & capture
150   
151    if(!cam.takePicture()) {
152      // Failed to take picture.  Show RED (+GREEN above) for 5 sec:
153      digitalWrite(RED_LED, HIGH);
154      delay(5000);
155      digitalWrite(RED_LED, LOW);
156      return; // Resume motion detection
157    }
158  
159    File imgFile = SD.open(filename, FILE_WRITE);
160    if(imgFile == NULL) {
161      // Couldn't open file.  Show RED (no GREEN) for 5 sec:
162      digitalWrite(GREEN_LED, LOW);
163      digitalWrite(RED_LED  , HIGH);
164      delay(5000);
165      digitalWrite(RED_LED  , LOW);
166      return; // Resume motion detection
167    }
168  
169    uint16_t jpegLen = cam.frameLength();
170    uint16_t bytesRemaining;
171    uint8_t  b, *ptr;
172  
173    // Transfer data from camera to SD file:
174    for(bytesRemaining = jpegLen; bytesRemaining ; bytesRemaining -= b) {
175      b   = min(32, bytesRemaining); // Max of 32 bytes at a time
176      ptr = cam.readPicture(b);      // From camera
177      imgFile.write(ptr, b);         // To SD card
178      digitalWrite(GREEN_LED, (bytesRemaining & 256) ? HIGH : LOW);
179    }
180  
181    // Pad file to minimum Eye-Fi file size if required:
182    if(jpegLen < minFileSize) {
183      for(bytesRemaining  = minFileSize - jpegLen; bytesRemaining ;
184          bytesRemaining -= b) {
185        b = min(32, bytesRemaining); // Max of 32 bytes at a time
186        imgFile.write(ptr, b);       // Just repeat last data, it's ignored
187        digitalWrite(GREEN_LED, (bytesRemaining & 256) ? HIGH : LOW);
188      }
189    }
190  
191    imgFile.close();
192  }
193  
194  
195  // Handler for unrecoverable errors (e.g. during hardware init).
196  // Flashes red LED at given speed.  Does not return!
197  void error(int time) {
198    digitalWrite(GREEN_LED, LOW);  // Green LED off
199    for(;;) {                      // Red LED flashes indefinitely
200      digitalWrite(RED_LED, HIGH);
201      delay(time);                 // Speed indicates error type
202      digitalWrite(RED_LED, LOW); 
203      delay(time);
204    }
205  }
206  
207  
208  // Scans image directory for next unused, available image filename.
209  // This is somewhat hacky-tacky...fine for an Arduino novelty sketch,
210  // but not totally bulletproof (e.g. does not create new directories
211  // if the current one is completely full, hardcoded index to image
212  // number, etc.).  On return, global var 'filename' contains the full
213  // absolute path to the next available image name.  The file is not
214  // opened here -- that's the responsibility of the calling function.
215  void nextFilename(void) {
216    // filename format is "DCIM/CANON999/IMG_nnnn.JPG";
217    // Start of image # is at pos            ^ 18
218    // If you decide to change the path or name, the index into
219    // filename[] will need to be changed to suit.
220    for(;;) {
221      // Screwy things will happen if over 10,000 images in folder.
222      // As explained above, this is not industrial-grade code.  It's
223      // expecting other limits (e.g. FAT16 stuff) will be hit first.
224  
225      // sprintf() is a costly function to invoke (about 2K of code),
226      // so this instead assembles the filename manually:
227      filename[18] = '0' +  imgNum / 1000;
228      filename[19] = '0' + (imgNum /  100) % 10;
229      filename[20] = '0' + (imgNum /   10) % 10;
230      filename[21] = '0' +  imgNum         % 10;
231      if(!SD.exists(filename)) return; // Name available!
232      imgNum++;                        // Keep looking
233    }
234  }
235  
236  
237  // Callback function for timestamps on SD files.
238  // Thanks to forum user 'fat16lib' for guidance!
239  void dateTime(uint16_t* date, uint16_t* time) {
240    DateTime now = clock.now();
241    *date = FAT_DATE(now.year(), now.month(), now.day());
242    *time = FAT_TIME(now.hour(), now.minute(), now.second());
243  }
244  
245  
246  // Timer1 interrupt handler for sleep throb
247  ISR(TIMER1_OVF_vect, ISR_NOBLOCK) {
248    // Sine table contains only first half...reflect for second half...
249    analogWrite(GREEN_LED, pgm_read_byte(&sleepTab[
250      (sleepPos >= sizeof(sleepTab)) ?
251      ((sizeof(sleepTab) - 1) * 2 - sleepPos) : sleepPos]));
252    if(++sleepPos >= ((sizeof(sleepTab) - 1) * 2)) sleepPos = 0; // Roll over
253    TIFR1 |= TOV1; // Clear Timer1 interrupt flag
254  }