diff --git a/code-snippets/client/ESPcam/.gitignore b/code-snippets/client/ESPcam/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..89cc49cbd652508924b868ea609fa8f6b758ec56 --- /dev/null +++ b/code-snippets/client/ESPcam/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/code-snippets/client/ESPcam/.vscode/extensions.json b/code-snippets/client/ESPcam/.vscode/extensions.json new file mode 100644 index 0000000000000000000000000000000000000000..080e70d08b9811fa743afe5094658dba0ed6b7c2 --- /dev/null +++ b/code-snippets/client/ESPcam/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/code-snippets/client/ESPcam/include/README b/code-snippets/client/ESPcam/include/README new file mode 100644 index 0000000000000000000000000000000000000000..194dcd43252dcbeb2044ee38510415041a0e7b47 --- /dev/null +++ b/code-snippets/client/ESPcam/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/code-snippets/client/ESPcam/platformio.ini b/code-snippets/client/ESPcam/platformio.ini new file mode 100644 index 0000000000000000000000000000000000000000..51be1376266f8dbbf7633667bf84b283f5403a6f --- /dev/null +++ b/code-snippets/client/ESPcam/platformio.ini @@ -0,0 +1,17 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32cam] +platform = espressif32 +board = esp32cam +framework = arduino +monitor_speed = 115200 +lib_deps = sensirion/arduino-sht@^1.2.2 + adafruit/RTClib @^2.1.1 \ No newline at end of file diff --git a/code-snippets/client/ESPcam/src/main.cpp b/code-snippets/client/ESPcam/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ac20e29084b8801963f6875cd65dda2241634643 --- /dev/null +++ b/code-snippets/client/ESPcam/src/main.cpp @@ -0,0 +1,321 @@ +/* +Snippet content: +Takes pictures with ESP32-CAM, saves images to MicroSD Card. +Measures air temperature and relative humidity from SHT85. + +We use the ESP-CAM for the SHT85 readout because of the +limited wire length that can be used for I2C sensors - the +camera sits at the top of the mast next to the SHT85 sensor. + +ESP-CAM code partially copied from +https://dronebotworkshop.com/esp32-cam-microsd/ + +NOTE: To flash the ESP-CAM, pin IO1 has to be connected to GND! +TODO: because of this limitation, it would be nice to implement +over-the-air updates for the camera at one point. +*/ + +// Include Required Libraries +// I2C: SHT85, RTC +#include <Arduino.h> +#include <Wire.h> +#include "SHTSensor.h" // sensirion/arduino-sht@^1.2.2 +#include "RTClib.h" // adafruit/RTClib @^2.1.1 +#include "SPI.h" + +// Camera libraries +#include "esp_camera.h" +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" +#include "driver/rtc_io.h" + +// MicroSD Libraries +#include "FS.h" +#include "SD_MMC.h" + +// Pin definitions for CAMERA_MODEL_AI_THINKER +#define PWDN_GPIO_NUM 32 +#define RESET_GPIO_NUM -1 +#define XCLK_GPIO_NUM 0 +#define SIOD_GPIO_NUM 26 +#define SIOC_GPIO_NUM 27 +#define Y9_GPIO_NUM 35 +#define Y8_GPIO_NUM 34 +#define Y7_GPIO_NUM 39 +#define Y6_GPIO_NUM 36 +#define Y5_GPIO_NUM 21 +#define Y4_GPIO_NUM 19 +#define Y3_GPIO_NUM 18 +#define Y2_GPIO_NUM 5 +#define VSYNC_GPIO_NUM 25 +#define HREF_GPIO_NUM 23 +#define PCLK_GPIO_NUM 22 + +// Pin definitions for I2C (SHT85, RTC) +// This is different from the pins on the ESP32-C3-DevKit boards! +#define SDA 12 +#define SCL 13 + +// LED control +#define LEDpin 4 + +// string for saving the time +char time_string[20]; + +// number of images to take: the last one is saved +// this is done because the first few images have a green tint otherwise +int img_number = 3; + +SHTSensor sht(SHTSensor::SHT85); // I2C address: 0x44 +RTC_DS3231 rtc; // I2C address: 0x68 +camera_config_t config; // camera configuration parameters + + + +void configESPCamera() { + // Configure Camera parameters + + config.ledc_channel = LEDC_CHANNEL_0; + config.ledc_timer = LEDC_TIMER_0; + config.pin_d0 = Y2_GPIO_NUM; + config.pin_d1 = Y3_GPIO_NUM; + config.pin_d2 = Y4_GPIO_NUM; + config.pin_d3 = Y5_GPIO_NUM; + config.pin_d4 = Y6_GPIO_NUM; + config.pin_d5 = Y7_GPIO_NUM; + config.pin_d6 = Y8_GPIO_NUM; + config.pin_d7 = Y9_GPIO_NUM; + config.pin_xclk = XCLK_GPIO_NUM; + config.pin_pclk = PCLK_GPIO_NUM; + config.pin_vsync = VSYNC_GPIO_NUM; + config.pin_href = HREF_GPIO_NUM; + config.pin_sscb_sda = SIOD_GPIO_NUM; + config.pin_sscb_scl = SIOC_GPIO_NUM; + config.pin_pwdn = PWDN_GPIO_NUM; + config.pin_reset = RESET_GPIO_NUM; + config.xclk_freq_hz = 20000000; + config.pixel_format = PIXFORMAT_JPEG; // Choices are YUV422, GRAYSCALE, RGB565, JPEG + + // Select lower framesize if the camera doesn't support PSRAM + if (psramFound()) { + config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA + config.jpeg_quality = 10; //10-63 lower number means higher quality + config.fb_count = 2; + } else { + config.frame_size = FRAMESIZE_SVGA; + config.jpeg_quality = 12; + config.fb_count = 1; + } + + // Initialize the Camera + esp_err_t err = esp_camera_init(&config); + + if (err != ESP_OK) { + Serial.printf("Camera init failed with error 0x%x", err); + return; + } + + // Camera quality adjustments + sensor_t * s = esp_camera_sensor_get(); + + // TODO: this is copied from an online tutorial, it's possible that we don't need this at all! + // BRIGHTNESS (-2 to 2) + s->set_brightness(s, 0); + // CONTRAST (-2 to 2) + s->set_contrast(s, 0); + // SATURATION (-2 to 2) + s->set_saturation(s, 0); + // SPECIAL EFFECTS (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia) + s->set_special_effect(s, 0); + // WHITE BALANCE (0 = Disable , 1 = Enable) + s->set_whitebal(s, 1); + // AWB GAIN (0 = Disable , 1 = Enable) + s->set_awb_gain(s, 1); + // WB MODES (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home) + s->set_wb_mode(s, 0); + // EXPOSURE CONTROLS (0 = Disable , 1 = Enable) + s->set_exposure_ctrl(s, 1); + // AEC2 (0 = Disable , 1 = Enable) + s->set_aec2(s, 0); + // AE LEVELS (-2 to 2) + s->set_ae_level(s, 0); + // AEC VALUES (0 to 1200) + s->set_aec_value(s, 300); + // GAIN CONTROLS (0 = Disable , 1 = Enable) + s->set_gain_ctrl(s, 1); + // AGC GAIN (0 to 30) + s->set_agc_gain(s, 0); + // GAIN CEILING (0 to 6) + s->set_gainceiling(s, (gainceiling_t)0); + // BPC (0 = Disable , 1 = Enable) + s->set_bpc(s, 0); + // WPC (0 = Disable , 1 = Enable) + s->set_wpc(s, 1); + // RAW GMA (0 = Disable , 1 = Enable) + s->set_raw_gma(s, 1); + // LENC (0 = Disable , 1 = Enable) + s->set_lenc(s, 1); + // HORIZ MIRROR (0 = Disable , 1 = Enable) + s->set_hmirror(s, 0); + // VERT FLIP (0 = Disable , 1 = Enable) + s->set_vflip(s, 0); + // DCW (0 = Disable , 1 = Enable) + s->set_dcw(s, 1); + // COLOR BAR PATTERN (0 = Disable , 1 = Enable) + s->set_colorbar(s, 0); +} + +void initMicroSDCard() { + // Start the MicroSD card + Serial.println("Mounting MicroSD Card"); + if (!SD_MMC.begin("/sdcard", true)) { // the arguments disable the LED when using the SD card - leave them there! + Serial.println("MicroSD Card Mount Failed"); + return; + } + uint8_t cardType = SD_MMC.cardType(); + if (cardType == CARD_NONE) { + Serial.println("No MicroSD Card found"); + return; + } +} + +void takeNewPhoto(String path) { + // Take picture with the camera and save it + + // Take multiple pictures with a short break in between + // necessary for the camera to auto-adjust settings + camera_fb_t * fb; + for (int i = 1; i <= img_number; i++) { + // Setup frame buffer + fb = esp_camera_fb_get(); + // digitalWrite(LEDpin, LOW); // disable flash LED if needed + + if (!fb) { + Serial.println("Camera capture failed"); + return; + } else { + Serial.println("Camera capture successful"); + } + // TODO check if this delay is necessary once the SD-card todo below + // is resolved. It's possible that it can be at least shortened. + if (i == img_number) { + delay(2000); + } + + esp_camera_fb_return(fb); + // Without this delay the image is corrupt + // TODO this delay can possibly also be optimized. + delay(1500); + + /* TODO: + The SD card throws the following error when the capture fails: + E (877528) sdmmc_cmd: sdmmc_write_sectors_dma: sdmmc_send_cmd returned 0x109 + E (877528) diskio_sdmmc: sdmmc_write_blocks failed (265) + This happens infrequently, say every 10th image or so. + If this happens, one more new image should be taken and saved + */ + } + + // Save picture to microSD card + fs::FS &fs = SD_MMC; + File file = fs.open(path.c_str(), FILE_WRITE); + if (!file) { + Serial.println("Failed to open file in write mode"); + } + else { + file.write(fb->buf, fb->len); // payload (image), payload length + Serial.printf("Saved file to path: %s\n", path.c_str()); + } + // Close the file + file.close(); + + // Return the frame buffer back to the driver for reuse + esp_camera_fb_return(fb); +} + + +void readSHT(){ + // Read data from the initialized SHT85 + // TODO send via esp now instead of printing + if (sht.readSample()) { + Serial.print("T: "); + Serial.println(sht.getTemperature(), 2); + Serial.print("RH: "); + Serial.println(sht.getHumidity(), 2); + } else { + Serial.print("Error in readSample()\n"); + } +} + +void readRTC(){ + // This is used for a picture timestamp (name of the image) + rtc.begin(); + DateTime now = rtc.now(); + sprintf(time_string, "%04d-%02d-%02dT%02d-%02d-%02d", now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second()); + Serial.println(time_string); +} + +void readI2C(){ + // Start the I2C bus + Wire.begin(SDA, SCL); + delay(100); // let serial console settle + + // initiate the SHT85 sensor + Serial.println(""); + if (sht.init()) { + Serial.println("SHT initialization successful"); + } else { + Serial.println("SHT initialization failed"); + } + + // T + RH reading + readSHT(); + // real-time clock reading + readRTC(); + delay(100); + + // end I2C to free the pins to be used by the SD card + Wire.end(); +} + + +void setup() { + // control of the LED pin + pinMode(LEDpin, OUTPUT); + // Disable brownout detector + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); + + // Start Serial + Serial.begin(115200); + + // Initialize the camera + Serial.print("Initializing the camera module..."); + configESPCamera(); + Serial.println("Camera OK!"); + +} + +void loop() { + // initiate I2C, read SHT+RTC, end I2C + readI2C(); + + // Initialize the MicroSD + Serial.print("Initializing the MicroSD card module... "); + initMicroSDCard(); + + // Path where new image will be saved in MicroSD card + String path = "/" + String(time_string) + ".jpg"; + Serial.printf("Picture file name: %s\n", path.c_str()); + + // Take and save a picture + takeNewPhoto(path); + + // Unmount SD card to free the pins for I2C use + SD_MMC.end(); + + // Delay for specified period + delay(1000); +} + + + diff --git a/code-snippets/client/ESPcam/test/README b/code-snippets/client/ESPcam/test/README new file mode 100644 index 0000000000000000000000000000000000000000..9b1e87bc67c90e7f09a92a3e855444b085c655a6 --- /dev/null +++ b/code-snippets/client/ESPcam/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html