It is the 1747th of March 2020 (aka the 11th of December 2024)
You are 18.97.9.169,
pleased to meet you!
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:
0
QQVGA (160×120)
1
2
QCIF (176x144)
3
HQVGA (240×176)
4
QVGA (320×240)
5
CIF (400×296)
6
VGA (640×480)
7
SVGA (800×600)
8
XGA (1024×768)
9
SXGA (1280×1024)
10
UXGA (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.
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:
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:
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...
And soon the other cherries...
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.
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)
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.