mailto: blog -at- heyrick -dot- eu

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:

Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺ ADDING COMMENTS DOES NOT WORK IF READING TRANSLATED VERSIONS.
 
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.

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?] . . . try the comment feed!
Your name
Your email (optional)
Validation Are you real? Please type 30696 backwards.
Your comment
French flagSpanish flagJapanese flag
Calendar
«   April 2023   »
MonTueWedThuFriSatSun
     
345678
10121314
1718192021
2425272829

Advent Calendar 2023
(YouTube playlist)

(Felicity? Marte? Find out!)

Last 5 entries

List all b.log entries

Return to the site index

Geekery

Search

Search Rick's b.log!

PS: Don't try to be clever.
It's a simple substring match.

Etc...

Last read at 23:49 on 2024/03/28.

QR code


Valid HTML 4.01 Transitional
Valid CSS
Valid RSS 2.0

 

© 2023 Rick Murray
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted.
RIPA notice: No consent is given for interception of page transmission.

 

Have you noticed the watermarks on pictures?
Next entry - 2023/04/02
Return to top of page