mailto: blog -at- heyrick -dot- eu

Navi: Previous entry Display calendar Next entry
Switch to desktop version

FYI! Last read at 09:08 on 2024/11/23.

Rick's LapseCam v0.02

Having used my camera briefly in testing, it was clear that a few changes were necessary.

The first is to add the option of a config.txt file in order to control some of the behaviour of the device without having to rebuild the firmware each time.

The format is simple. One or more lines that consist of a lowercase letter followed by an equals followed by a number (in base 10).

f=Set frame size, range 0 - 10:
0QQVGA (160×120)
1
2QCIF (176x144)
3HQVGA (240×176)
4QVGA (320×240)
5CIF (400×296)
6VGA (640×480)
7SVGA (800×600)
8XGA (1024×768)
9SXGA (1280×1024)
10UXGA (1600×1200) - default
q=Set quality, range 1 - 63.
Default quality is 5.
i=Set interval in seconds, range 15 - ...
Default interval is 60 seconds.

For example, here is a file that sets a lower quality SVGA picture to be taken every 30 seconds:

f=7
q=15
i=30

 

Another issue that appeared, since my little µSD card is only 128MB, is that when the media is full, the camera will continue trying to write files to the disc, as it is capable of creating files, but not writing data to them.

Therefore, the amount of data written is now checked, and if there's a discrepancy, the camera will halt with a slow blinking red notification LED.

 

As this required numerous changes to the source, I'll give a brief overview of them here, but it's best if you download the archive with the sketch within.

Initial variables

At the top of setup()...

   int      counter = 0;
   char     filename[32] = "";
   int64_t  ticks = esp_timer_get_time();
   uint64_t interval = 60000000;        // default 1 minute
   uint8_t  sdtype = CARD_NONE;
   uint64_t sdsize = 0;
   uint64_t sdfree = 0;
   int      quality = 5;                // default
   int      framesize = FRAMESIZE_UXGA; // default
   char     confbuff[8] = "";
   int      confread = 0;
   int      confval = 0;
   int      byteswritten = 0;

 

Handling the config.txt file

After checking for the "reset.txt" file, insert this to handle the "config.txt" file.

   // Is the "config.txt" file present?
   if ( fs.exists("/config.txt") )
   {
      Serial.println("The config.txt file is present, loading settings.");

      File conffile = fs.open("/config.txt", FILE_READ);
      if ( !conffile )
      {
         Serial.println("Failed to open config.txt, using defaults.");
      }
      else
      {
         while ( conffile.available() )
         {
            confread = conffile.readBytesUntil('\n', confbuff, 8);

            if ( confread > 0 )
            {
               // f=<num> to set framesize
               if ( tolower(confbuff[0]) == 'f' )
               {
                  confval = atoi(confbuff + 2);
                  if ( ( confval > 0 ) && ( confval < 11 ) )
                  {
                     // This relates to the ORIGINAL resolutions
                     // list. The use of the defines is because
                     // the list doesn't match what's there now.
                     Serial.print("-> Setting framesize to ");
                     switch (confval)
                     {
                        case  0 : // fall through
                        case  1 : Serial.println("QQVGA (160x120).");
                                  confval = FRAMESIZE_QQVGA;
                                  // 1 = QQVGA2 (128x160), this was
                                  //     an odd size, not present in
                                  //     current builds.
                                  break;
                        case  2 : Serial.println("QCIF (176x144).");
                                  confval = FRAMESIZE_QCIF;
                                  break;
                        case  3 : Serial.println("HQVGA (240x176).");
                                  confval = FRAMESIZE_HQVGA;
                                  break;
                        case  4 : Serial.println("QVGA (320x240).");
                                  confval = FRAMESIZE_QVGA;
                                  break;
                        case  5 : Serial.println("CIF (400x296).");
                                  confval = FRAMESIZE_CIF;
                                  break;
                        case  6 : Serial.println("VGA (640x480).");
                                  confval = FRAMESIZE_VGA;
                                  break;
                        case  7 : Serial.println("SVGA (800x600).");
                                  confval = FRAMESIZE_SVGA;
                                  break;
                        case  8 : Serial.println("XGA (1024x768).");
                                  confval = FRAMESIZE_XGA;
                                  break;
                        case  9 : Serial.println("SXGA (1280x1024).");
                                  confval = FRAMESIZE_SXGA;
                                  break;
                        case 10 : Serial.println("UXGA (1600x1200).");
                                  confval = FRAMESIZE_UXGA;
                                  break;
                        // We do not support 96x96 and HD (1280x720)
                        // as these are newer than my SDK.
                     }
                     framesize = confval;
                  }
                  else
                  {
                     Serial.printf("Invalid framesize %d.\n", confval);
                  }
               }

               // q=<num> to set quality
               if ( tolower(confbuff[0]) == 'q' )
               {
                  confval = atoi(confbuff + 2);
                  if ( ( confval > 0 ) && ( confval < 64 ) )
                  {
                     Serial.printf("-> Setting quality to %d.\n", confval);
                     quality = confval;
                  }
                  else
                  {
                     Serial.printf("Invalid quality %d.\n", confval);
                  }
               }

               // i=<num> to set timer interval
               if ( tolower(confbuff[0]) == 'i' )
               {
                  confval = atoi(confbuff + 2);
                  if ( confval < 15 )
                  {
                     Serial.printf("Interval must be at least 15 seconds.\n");
                     confval = 15;
                  }
                  Serial.printf("-> Setting timer interval to %d seconds.\n", confval);

                  interval = (uint64_t)confval * (uint64_t)1000000;
               }
            } // if (confread > 0 )
         } // while ( fs.available() )

         conffile.close();
      } // if config file opened
   } // if config file exists

 

Setting the camera options

Just below the above, amend the camera setup as follows:

   // Set the camera options
   sensor_t * s = esp_camera_sensor_get();
   s->set_framesize(s, (framesize_t)framesize);
   s->set_quality(s, quality);
   // other options can just be the camera defaults

 

Checking file writes

Change the block of code for writing to the file to include testing that what was wanted to be written was in fact written:

   {
      byteswritten = file.write(fb->buf,     fb->len);
      file.close();          // buffer data, buffer length

      Serial.printf("Saved %d of %d bytes, updating counter.\n",
                    byteswritten, fb->len);

      // Trap if data couldn't be written (usually means disc full)
      if ( byteswritten != fb->len )
      {
         Serial.println("ERROR: Unable to write all data - disc full?");
         while (true)
         {
            // Just loop, slow LED flash, it's a show stopper
            digitalWrite(LED_PIN, LED_ON);
            delay(500);
            digitalWrite(LED_PIN, LED_OFF);
            delay(500);
         }
      }

      // If we're here, file saved correctly, so update
      // the sequential counter and commit it to EEPROM.
      counter++;
      EEPROM.write(0,  (counter       & 255));
      EEPROM.write(1, ((counter >> 8) & 255));
      EEPROM.commit();
   }

 

Variable sleep interval

Finally, changethe RTC timer and final status as shown:

   // Tell the RTC timer to prod us when the time has elapsed
   ticks = esp_timer_get_time() - ticks; // our elapsed time
   ticks = interval - ticks; // how much remains of our interval time
   esp_sleep_enable_timer_wakeup(ticks);


   // Final status
   Serial.println("Going into deep sleep for %llu seconds, seeya!", (ticks / 1000000));
   delay(500);
   Serial.flush();

 

That's it. Now you can use the presence of the file reset.txt to reset the counter, and information in the config.txt file to control the resolution, quality, and sleep interval.

 

Two timelapse videos

This was taken yesterday afternoon to test everything works. 284 pictures (all that would fit on my 128MB µSD card) with one picture every minute, giving a recording of just under five hours.

 

This one was taken this morning. I got up at about twenty past six to feed Anna and make my morning tea, so I set LapseCam running. Similar frame count, similar duration.

 

Making a video from the pictures

Copy the pictures so they're all in the same directory (on a PC, Android phone, whatever).

Then use FFMPEG to convert the pictures to a video.

On Windows, assuming C:\LapseCam\source is where the pictures are:

ffmpeg -framerate 15 -i "c:\LapseCam\source\image%05d.jpg" -pix_fmt yuv420p "c:\LapseCam\video.mp4"

On Android, with the FFMPEG app by SilentLexx UA (it's a green icon with the ffmpeg logo in a white Android robot), click on the little >_ icon at the bottom right to control FFMPEG directly, and then enter:

ffmpeg -framerate 15 -i "/storage/emulated/0/LapseCam/source/image%05d.jpg" -pix_fmt yuv420p "/storage/emulated/0/LapseCam/video.mp4"
This assumes, also, the pictures are in LapseCam\source in the root of your accessible storage.

Feel free to play with the framerate. My 280 pictures gives just under 19 seconds at 15fps. At 30fps it would be a smoother video, but only nine and a half seconds.

Fudging the colour space to YUV (Y'CbCr) with 4:2:0 chroma subsampling (-pix_fmt yuv420p) is necessary as a fair number of players will struggle with other subsampling methods. JPEGs normally use 4:2:0, but this isn't guaranteed. So it's best to force it for maximum compatibility.

 

Soon Sakura

Soon the Sakura cherry...

Wait for it, wait for it...
Wait for it, wait for it...

And soon the other cherries...

Keep waiting for it...
Keep waiting for it...

These pictures were taken in the brief time the sun was out and it was neither dreary-grey and/or raining.

Saw this, saw the opportunity, liked.

Nice patterns and colours.
Nice patterns and colours.

 

I was planning on going shopping today, but I don't really need to (have enough milk) so I won't. But... it's chilly (only 10°C), grey, miserable, raining now and then...
...I guess there's no other choice except to crawl under my duvet with a mug of tea and fire up Netflix.

 

 

Your comments:

David Pilling, 3rd April 2023, 15:27
I used "Virtual Dub" to take a folder of jpegs and convert them to a video. (free, video edit software)

Add a comment (v0.11) [help?]
Your name:

 
Your email (optional):

 
Validation:
Please type 03654 backwards.

 
Your comment:

 

Navi: Previous entry Display calendar Next entry
Switch to desktop version

Search:

See the rest of HeyRick :-)