Turn ESP32-CAM Into a Snapshot Taker, for Selfies and Time Lapse Pictures
by Trevor Lee in Circuits > Arduino
568 Views, 7 Favorites, 0 Comments
Turn ESP32-CAM Into a Snapshot Taker, for Selfies and Time Lapse Pictures
This project is an attempt to turn ESP32-CAM (or similar ones like LILYGO T-Camera / T-Camera Plus) microcontroller board into an Android phone-managed snapshot taker, for snapshots like selfies and time-lapse pictures ... maybe ... just for fun!
With your Android phone, connect to the ESP32-CAM, make your post and smile. Then go back to your phone to select the pictures you like best and save to your phone directly.
With your Android phone, connect to the ESP32-CAM, set how frequently a snapshot is to be taken. Then disconnect from the ESP32-CAM and let it do its job of taking time-lapse pictures. Then reconnect to the ESP32-CAM to transfer the taken pictures to your phone.
The microcontroller program (sketch) is developed with Arduino framework using VS Code and PlatformIO, in the similar fashion as described by the post -- A Way to Run Arduino Sketch With VSCode PlatformIO Directly
The remote UI -- control panel -- is realized with the help of the DumbDisplay Android app. You have a choice of using Bluetooth (classic) or WiFi for the connection. For a brief description of DumbDisplay, please refer to the post -- Blink Test With Virtual Display, DumbDisplay
Please note that the UI is driven by the sketch; i.e., the flow of the UI is programmed in the sketch. Other than installing the DumbDisplay Android app, no building of a separate mobile app is necessary.
The UI -- Ways of Taking Snapshots
There are 3 ways snapshots can be captured and saved to your phone
- With your phone connected to the ESP32-CAM -- certainly through the DumbDisplay Android app with Bluetooth or WiFi -- you click the 💾Save button of the UI to save the image being shown. You can turn on / off the auto-save feature by clicking Auto❎ / Auto☑️. With auto-save enabled, whenever a snapshot is captured from the ESP32-CAM, it will be saved to your phone
- Assuming you have connected your phone with the ESP32-CAM, clicking on the image canvas will pause showing snapshots, and you will be provided with a slider to slide back in time to select the previously captured snapshots to save to your phone. You will have 20 such snapshots that you can slide back in time to. Once you see the snapshots you like, click 💾Save to save it. When you are done, you can go back by clicking ❌Cancel (or double click on the image canvas)
- Select Offline📴 to enable "offline" capturing and saving of snapshots to the ESP32-CAM. With "offline" enabled, when you disconnect (i.e. offline), the ESP32-CAM will start capturing "offline" snapshots saving them to its flash memory (or SD card). Whenever you reconnect, you will be asked if you want to transfer the saved "offline" snapshots to your phone. Better yet, in SD card case, you can physically transfer the "offline" snapshot JPEG files saved the usual way -- insert the SD card to your computer, copy and delete the JPEG files on your SD card. One point about using the SD card slot of ESP32-CAM -- since the SD card module of ESP32-CAM shares the same pin 4 used by the flashlight, whenever the SD card is accessed, the flashlight will light up bright (hence it is not a feature, but it is how it is)
The snapshots will be saved in JPEG format:
- Snapshots saved to your phone will be saved to the private folder of DumbDisplay app - /<main-storage>/Android/data/nobody.trevorlee.dumbdisplay/files/Pictures/DumbDisplay/snaps/ -- with file name like 20240904_223249_001.jpg (YYYYMMDD-hhmmss_seq.jpg)
- Offline snapshots transferred to your phone will be saved to a subfolder of the above-mentioned private folder of DumbDisplay app. The name of the subfolder depends on the time you do the snapshots transfer, and is like 20240904_231329_OFF.
- Offline snapshots are saved to your ESP32-CAM's flash memory / SD card with name like off_001.jpg. Note that the "offline" JPEG files will only be properly time-stamped if there was no reset / reboot of ESP32-CAM after connecting to your phone.
By default, the storage for DumbDisplay app is a private folder, which DumbDisplay app needs to initialize. You can do this By selecting the Settings menu item of DumbDisplay app, and clicking on the Media Storage button
And you can use a folder manager app, like Files by Marc apps & software to browse to that folder
The UI -- Frequency of Capturing Snapshots
Actually, snapshot capturing is continuous (as fast as possible), but captured snapshot shipments to your phone is not as smooth; in fact there is a setting on how frequently a snapshot is shipped to your phone. Depending on the resolution / quality of the snapshot and the connection method / condition, it can be as frequent as 5 snapshots per second. Treat this frequency control as a feature that allows taking of time-lapse pictures at a desired frequency 😁
There are 4 quick selections of frequency / frame rate -- 5 PS / 2 PS / 1 PS / 30 PM corresponding to 5 frames per second / 2 frames per second / 1 frame per second / 30 frames per minute
And there is a custom per-hour frame rate you can set and select -- 720 PH (720 is the default). Once you click on the 720 PH selection, your phone's virtual keyword will pop up allowing you to enter the value (1 - 3600) you want (an empty value means previously set value).
The UI -- Snapshots Quality Adjustments
There are several camera adjustments that will affect the quality of the captured snapshots:
- Snapshot resolution / size selections:
- QVGA (320x240)
- VGA (640x480)
- SVGA (800x600)
- XGA (1024x768)
- HD (1280x720)
- SXGA (1280x1024)
- UXGA (1600x1200)
- JPEG compression quality slider 🖼️ -- from 5 (high quality) to 60 (low quality)
- Brightness slider ☀️ -- from -2 (dim) to 2 (bright)
Note that the higher the snapshots quality / resolution, the more data needs be shipped from the ESP32-CAM to your phone, making the UI less responsive, especially when WiFi (not Bluetooth) since WiFi is not full-duplex.
The UI -- ESP32 Idle Sleep
Another feature is that when "offline" snapshot capturing is enabled, after a while (60 seconds) of being idle (i.e. not connected), the ESP32-CAM will be put to sleep mode in order to save power. Of course, since "offline" capturing is enabled, the ESP32-CAM will wake up in due time to take a snapshot. However if "offline" frequency is too high, higher than 12 frames per minute, the ESP32-CAM will stay on.
In any case, if you want to connect to the ESP32-CAM when it is in sleep mode, you will need to reset / reboot the ESP32-CAM first
Developing and Building
As mentioned previously, the sketch will be developed using VS Code and PlatformIO. Please clone the PlatformIO project ESP32CamSnapper GitHub repository.
The configurations for developing and building of the sketch are basically written down in the platformio.ini file
Please make sure you select the correct PlatformIO project environment -- ESP32CAM / TCAMERA / TCAMERAPLUS
The program entry point is src/main.cpp
The above main.cpp assumes that you prefer to use Bluetooth (classic), and defines the Bluetooth device name to be ESP32CamSnapper
Note that, for the purposes of this sketch, Bluetooth (classic) is the suggested way of connecting with DumbDisplay app.
However, if you prefer to use WiFi, you will need to define WIFI_SSID and WIFI_PASSWORD rather than BLUETOOTH
In case of WiFi, you will need to find out the IP of your ESP32-CAM in order to make connect to it. This can be done easily by attaching it to a Serial monitor (set to baud-rate 115200). There, when your ESP32-CAM tries to connect to DumbDisplay app, log lines like below will be printed:
That IP address is the IP address of your ESP32-CAM to make connection to.
If you have SD card installed for ESP32-CAM (or LILYGO T-Camera Plus), you can use it to store "offline" snapshots, rather than the limited flash memory of the microcontroller board.
To configure the sketch for such setup, you will need to define the macro OFFLINE_USE_SD, like by uncomment the corresponding line of the sketch
or put the #define line in main.cpp like
The Sketch
The sketch of the project is esp32camsnapper/esp32camsnapper.ino. There are several customizations you may want to make:
- By default, you have 20 snapshots you can go back in time to. This number is controlled by the macro STREAM_KEEP_IMAGE_COUNT
- By default, you can enable "offline" snapshot capturing. If you don't want "offline" at all, you can comment out the corresponding #define
- By default, the ESP32-CAM will be put to sleep after 60 seconds being idle (i.e. not connected). You can change it to other value by changing the macro IDLE_SLEEP_SECS. If this 'idle put to sleep' behavior is not desirable, you can comment out that #define to disable such behavior
- Most settings you change in the UI will be persisted to the EEPROM of the ESP32-CAM. These settings will be effective even you re-upload the program built. In case you want to reset those settings to their defaults, change the HEADER to some other value, build and re-upload
Sketch Highlight -- EEPROM
Firstly, in order to use the EEPROM, you will first need to reserve some number of bytes for it, like: void setup() { Serial.begin(115200); EEPROM.begin(32); ... } Here, 32 bytes are reserved for the purpose.
Most settings are persisted to the EEPROM of ESP32-CAM like
Note that:
- Long is 4 bytes in size, and can be used for int32_t
- Char is 1 byte in size, and can be used for int8_t
- Bool is also 1 byte in size, and can be used for bool
- Short is 2 bytes in size, and can be used for int16_t
And these settings are read back when ESP32-CAM starts up like
Sketch Highlight -- ESP32 Sleep Mode
ESP32 can be put to sleep like
When it wakes up, the sketch / program will be just like freshly started, except for variables like
Note that the variable prefixed with RTC_DATA_ATTR will keep its value through sleep.
Sketch Highlight -- Camera
The ESP32-CAM Camera is initialized with initializeCamera
It is important that `pixel_format` be set to `PIXFORMAT_JPEG`. Also notice that at the end of initializeCamera, resetCameraImageSettings is called to set the various settings of the Camera, which is also called when you change camera settings with the UI
Concerning capturing snapshots, here is how an "offline" snapshot is taken when ESP32-CAM wakes up from sleep
Notice:
- the value of wakeupOfflineSnapMillis is used to determine whether the program is started due to normal boot up of wake up; it is set to the value of "how many milliseconds to sleep for next "offline" snap
- certain, initializeCamera will be called to initialize the camera
- after initializing the camera, the camera is given some time (2 seconds) to be ready
- then the camera's buffer is retrieved by calling esp_camera_fb_get, which contains the bytes (JPEG bytes) to be saved by calling saveOfflineSnap
- after saving the bytes, the buffered is returned by calling esp_camera_fb_return
- at last, it goes back to sleep again
You will find that the tutorial Change ESP32-CAM OV2640 Camera Settings: Brightness, Resolution, Quality, Contrast, and More by Random Nerd Tutorials gives more details on ESP32-CAM.
Sketch Highlight -- DumbDisplay
Like all other use cases of using DumbDisplay, you first declare a global DumbDisplay object dumbdisplay
In WiFi case, normally, you will use the defaults for sendBufferSize (2nd parameter) and idleTimeout (3rd parameter). However for this program, since a large amount of data might be shipped to DumbDisplay app, the idleTimeout better be longer than the default.
Then, several global helper objects / pointers are declared
The construction of DDMasterResetPassiveConnectionHelper accepts two parameters
- The DumbDisplay object
- Whether to call DumbDisplay::recordLayerCommands() / DumbDisplay::playbackLayerCommands() before and after calling "initialize" lambda expression. The effect of calling DumbDisplay::recordLayerCommands() / DumbDisplay::playbackLayerCommands() is so the UI construction of the various layers is more smooth.
The life-cycle of the above DumbDisplay layers and "tunnels" are managed by the global pdd object, which will monitor connection and disconnection of DumbDisplay app, calling appropriate C++ lambda expressions in the appropriate time. It is cooperatively given "time slices" in the loop() block like
- pdd.loop() accepts 3 C++ lambda expressions -- []() { ... }
- the 1st lambda expression is called when DumbDisplay app is connected, and DumbDisplay components need be initialized. Here initializeDD() is called for the purpose.
- the 2nd lambda expression is called during DumbDisplay app is connected to update the DumbDisplay components. Here, updatedDD() for the purpose. Note that pdd.firstUpdated() tells if this 2nd lambda expression was previously called after DumbDisplay component initialization (hence not of that means first call)
- the 3rd optional lambda expression is called when DumbDisplay app is disconnected. Here, deinitializeDD() is called for the purpose.
- After pdd.loop() is the code that will be run whether DumbDisplay app is connected or not.
- pdd.isIdle() tells if DumbDisplay is not connected (i.e. idle). In idle case, the function handleIdle() is called. Note that pdd.justBecameIdle() tells if DumbDisplay just disconnected making it idle.
The first time when the ESP32-CAM is connected, ESP32-CAM's clock is synchronized with that of your phone via generalTunnel -- a "general tunnel"
Note that syncing and setting of the ESP32-CAM's clock is only done once when the ESP32-CAM connects with your phone. Also note that the ESP32-CAM's clock will keep running during sleep.
Other than the main scene / state, the UI has other scenes, like for selecting snapshots to save, and for transferring "offline" snapshots. When switching between the different scenes, the UI layers will be re-auto-pined by calling pinLayers
The last parameter of configAutoPin is autoShowHideLayers. Which if true, auto-pinning will automatically hide all other not-involved layers. This relieves the need to explicitly hide all layers not involved in different scenes of the UI.
Selection and prompting for custom frame rate might be worth highlighting here. It is accomplished with the customFrameRateSelectionLayer layer
- normally the type of "feedback" (fb->type) from the UI will not be CUSTOM
- in such a non-CUSTOM case (i.e. "feedback" is because of clicking), call customFrameRateSelectionLayer->explicitFeedback like above
- explicitFeedback will explicitly fake a "feedback" to the layer
- but this time, the type of "feedback" is CUSTOM -- the 4th parameter
- the first 3 parameters are for x, y, and text of the "feedback"
- the last parameters -- option to explicitFeedback numkeys -- means that during the "feedback" routing, you will be prompted for numeric value with your phone's virtual keyboard, which will then replace the text of the "feedback"
- note that the initial text passed in to explicitFeedback is used as the "hint" on the virtual keyboard
- in CUSTOM case, fb->text will be the value you entered
Another opportunity of prompting is confirmation for transferring "offline" snapshots to your phone. It is accomplished via generalTunnel like
- Right after gotten the "sync time" from DumbDisplay app, and when need to prompt for "yes/no" confirmation for transferring "offline" snaps, generalTunnel->reconnectTo is called to handle the "confirm" request.
- The "Yes" or "No" response will come back through generalTunnel again (the same way as DD_CONNECT_FOR_GET_DATE_TIME)
After the "offline" transfer, an "alert" is popped up indicating the transfer is done. This is done by calling dumbdispaly.alert
Build and Upload
Build, upload the sketch and try it out!
If it interests you, you may use a Serial monitor (set to baud-rate 115200) to observe when things are happening
Demo
Enjoy!
Hope that you will have fun with it! Enjoy!
Peace be with you! May God bless you! Jesus loves you! Amazing Grace!