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 }