ESP32-CAM: фото на MicroSD-карту с датой и временем (timestamp)
В этом уроке вы научитесь делать снимки с помощью ESP32-CAM и сохранять их на карту microSD. Мы будем включать текущую дату и время в имя файла. Использование даты и времени в имени файла полезно, потому что каждый снимок будет иметь уникальное имя, что означает отсутствие проблем с перезаписью существующих файлов; вы будете знать, когда был сделан каждый снимок; и вам будет проще организовывать или находить ваши файлы позже.
Предварительные требования
Прежде чем приступить к этому руководству, ознакомьтесь со следующими предварительными требованиями.
ESP32-CAM
Для этого проекта мы будем использовать плату ESP32-CAM AI-Thinker.
Если вы ещё не знакомы с ESP32-CAM, можете ознакомиться со следующими уроками:
Вы также можете использовать другую плату ESP32 с камерой — главное, чтобы она поддерживала карту microSD и вы скорректировали распиновку в коде. Этот проект совместим только с камерами OV2640.
Arduino IDE
Мы будем программировать плату ESP32 с помощью Arduino IDE. Для этого вам нужна установленная Arduino IDE, а также дополнение для ESP32. Если вы ещё не установили дополнение для ESP32, следуйте инструкции:
MicroSD-карта
Мы будем сохранять фотографии, сделанные с помощью ESP32-CAM, на карту microSD. Ваша карта microSD должна быть отформатирована в FAT32.
Чтобы ознакомиться с работой файлов на карте microSD с ESP32, посмотрите следующий урок:
Получение даты и времени с ESP32 (точный часовой пояс и летнее время)
Мы будем использовать текущую дату и время в именах файлов фотографий. У нас есть подробный урок, объясняющий, как настроить часовой пояс и летнее время при получении времени с NTP-сервера с помощью ESP32:
Обзор проекта
На следующей диаграмме показан общий обзор примера, который мы будем создавать.
В этом уроке мы покажем простой пример, который выполняет следующие действия:
ESP32-CAM подключается к вашему маршрутизатору по Wi-Fi (это необходимо для получения времени с NTP-сервера);
ESP32 подключается к NTP-серверу для инициализации даты и времени с правильным часовым поясом;
Инициализируются камера и карта microSD;
Камера делает новый снимок;
Получается текущая дата и время с NTP-сервера;
Фотография сохраняется на карту microSD — имя файла содержит дату и время съёмки (это уникальное имя файла);
Шаги 4-6 повторяются каждые 10 секунд. Для демонстрации ESP32-CAM будет выполнять эту задачу снова и снова в
loop(). Идея состоит в том, чтобы применить концепции, изученные в этом примере, к вашим собственным проектам.
Использование даты и времени в имени файла фотографии имеет несколько преимуществ:
все фотографии будут иметь разные имена файлов — у вас не будет проблем с перезаписью предыдущих файлов при перезагрузке ESP32;
вы будете знать, когда была сделана каждая фотография;
позже будет проще организовать ваши фотографии.
Необходимые компоненты
Для выполнения этого урока вам понадобится модуль ESP32 с камерой, поддерживающий карту microSD, сама карта microSD и оборудование для загрузки кода на плату (это может быть программатор ESP32-CAM MB или FTDI-программатор).
ESP32-CAM с OV2640
MicroSD-карта
Для загрузки кода на плату:
Программатор ESP32-CAM MB
Или FTDI-программатор и провода «мама-мама»
Код
Скопируйте следующий код в вашу Arduino IDE. Перед загрузкой кода на плату вам нужно указать ваш SSID и пароль, а также строку часового пояса для установки правильного часового пояса для вашего местоположения (подробнее о настройке строки часового пояса ниже).
/*********
Rui Santos
Complete instructions at https://RandomNerdTutorials.com/esp32-cam-photo-microsd-card-timestamp
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/
#include "esp_camera.h"
#include "FS.h" // SD Card ESP32
#include "SD_MMC.h" // SD Card ESP32
#include "soc/soc.h" // Disable brownout problems
#include "soc/rtc_cntl_reg.h" // Disable brownout problems
#include "driver/rtc_io.h"
#include <WiFi.h>
#include "time.h"
// REPLACE WITH YOUR NETWORK CREDENTIALS
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// REPLACE WITH YOUR TIMEZONE STRING: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
String myTimezone ="WET0WEST,M3.5.0/1,M10.5.0";
// Pin definition for CAMERA_MODEL_AI_THINKER
// Change pin definition if you're using another ESP32 camera module
#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
// Stores the camera configuration parameters
camera_config_t config;
// Initializes the camera
void configInitCamera(){
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_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_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; //YUV422,GRAYSCALE,RGB565,JPEG
config.grab_mode = CAMERA_GRAB_LATEST;
// 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; //0-63 lower number means higher quality
config.fb_count = 1;
} 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;
}
}
// Connect to wifi
void initWiFi(){
WiFi.begin(ssid, password);
Serial.println("Connecting Wifi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
}
// Function to set timezone
void setTimezone(String timezone){
Serial.printf(" Setting Timezone to %s\n",timezone.c_str());
setenv("TZ",timezone.c_str(),1); // Now adjust the TZ. Clock settings are adjusted to show the new local time
tzset();
}
// Connect to NTP server and adjust timezone
void initTime(String timezone){
struct tm timeinfo;
Serial.println("Setting up time");
configTime(0, 0, "pool.ntp.org"); // First connect to NTP server, with 0 TZ offset
if(!getLocalTime(&timeinfo)){
Serial.println(" Failed to obtain time");
return;
}
Serial.println("Got the time from NTP");
// Now we can set the real timezone
setTimezone(timezone);
}
// Get the picture filename based on the current ime
String getPictureFilename(){
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
return "";
}
char timeString[20];
strftime(timeString, sizeof(timeString), "%Y-%m-%d_%H-%M-%S", &timeinfo);
Serial.println(timeString);
String filename = "/picture_" + String(timeString) +".jpg";
return filename;
}
// Initialize the micro SD card
void initMicroSDCard(){
// Start Micro SD card
Serial.println("Starting SD Card");
if(!SD_MMC.begin()){
Serial.println("SD Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD Card attached");
return;
}
}
// Take photo and save to microSD card
void takeSavePhoto(){
// Take Picture with Camera
camera_fb_t * fb = esp_camera_fb_get();
//Uncomment the following lines if you're getting old pictures
//esp_camera_fb_return(fb); // dispose the buffered image
//fb = NULL; // reset to capture errors
//fb = esp_camera_fb_get();
if(!fb) {
Serial.println("Camera capture failed");
delay(1000);
ESP.restart();
}
// Path where new picture will be saved in SD Card
String path = getPictureFilename();
Serial.printf("Picture file name: %s\n", path.c_str());
// Save picture to microSD card
fs::FS &fs = SD_MMC;
File file = fs.open(path.c_str(),FILE_WRITE);
if(!file){
Serial.printf("Failed to open file in writing mode");
}
else {
file.write(fb->buf, fb->len); // payload (image), payload length
Serial.printf("Saved: %s\n", path.c_str());
}
file.close();
esp_camera_fb_return(fb);
}
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detector
Serial.begin(115200);
delay(2000);
// Initialize Wi-Fi
initWiFi();
// Initialize time with timezone
initTime(myTimezone);
// Initialize the camera
Serial.print("Initializing the camera module...");
configInitCamera();
Serial.println("Ok!");
// Initialize MicroSD
Serial.print("Initializing the MicroSD card module... ");
initMicroSDCard();
}
void loop() {
// Take and Save Photo
takeSavePhoto();
delay(10000);
}
Как работает код
Продолжайте чтение, чтобы узнать, как работает код, или перейдите к разделу Демонстрация.
Подключение библиотек
Сначала подключите необходимые библиотеки для работы с камерой, картой microSD и временем.
#include "esp_camera.h"
#include "FS.h" // SD Card ESP32
#include "SD_MMC.h" // SD Card ESP32
#include "soc/soc.h" // Disable brownout problems
#include "soc/rtc_cntl_reg.h" // Disable brownout problems
#include "driver/rtc_io.h"
#include <WiFi.h>
#include "time.h"
Сетевые учётные данные
Вставьте ваши сетевые учётные данные в следующие переменные.
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Строка часового пояса
Вставьте строку вашего часового пояса в переменную myTimezone. Вы можете посмотреть список строк часовых поясов здесь. Например, я живу в Порту. Часовой пояс — Europe/Lisbon. Из списка строк часовых поясов я вижу, что строка часового пояса для моего местоположения — WET0WEST,M3.5.0/1,M10.5.0.
//REPLACE WITH YOUR TIMEZONE STRING: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
String myTimezone ="WET0WEST,M3.5.0/1,M10.5.0";
Распиновка камеры и конфигурация
Далее укажите определение пинов для используемой модели камеры. Для модуля ESP32-CAM AI-Thinker это следующее определение пинов (измените его, если используете другой модуль ESP32 с камерой):
// Pin definition for CAMERA_MODEL_AI_THINKER
// Change pin definition if you're using another ESP32 camera module
#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
Определение пинов для различных плат ESP32 с камерой вы можете найти здесь: ESP32-CAM Camera Boards: Pin and GPIOs Assignment Guide.
Следующая строка необходима для сохранения конфигурации камеры. Мы добавим определение пинов и настройки изображения позже в коде.
// Stores the camera configuration parameters
camera_config_t config;
Настройка и инициализация камеры
Функция configInitCamera() начинает с назначения GPIO, которые вы определили ранее, а также формата изображения.
// Initializes the camera
void configInitCamera(){
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; //YUV422,GRAYSCALE,RGB565,JPEG
config.grab_mode = CAMERA_GRAB_LATEST;
Формат изображения определяется в следующей строке:
config.pixel_format = PIXFORMAT_JPEG; //YUV422,GRAYSCALE,RGB565,JPEG
Он может быть установлен в один из следующих форматов:
PIXFORMAT_JPEG
PIXFORMAT_YUV422
PIXFORMAT_GRAYSCALE
PIXFORMAT_RGB565
PIXFORMAT_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; //0-63 lower number means higher quality
config.fb_count = 1;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
Если модель камеры поддерживает PSRAM, мы устанавливаем размер кадра UXGA (1600x1200) и качество изображения 1.
Вы можете установить размер кадра в один из этих вариантов:
FRAMESIZE_UXGA (1600 x 1200)
FRAMESIZE_QVGA (320 x 240)
FRAMESIZE_CIF (352 x 288)
FRAMESIZE_VGA (640 x 480)
FRAMESIZE_SVGA (800 x 600)
FRAMESIZE_XGA (1024 x 768)
FRAMESIZE_SXGA (1280 x 1024)
Качество изображения может быть числом от 0 до 63. Меньшее число означает более высокое качество.
Важно: очень низкие числа для качества изображения, особенно при более высоком разрешении, могут привести к сбою ESP32-CAM, или камера может не сделать снимки корректно. Поэтому, если вы заметите, что изображения, сделанные ESP32-CAM, обрезаны пополам или имеют странные цвета, это, вероятно, означает, что вам нужно снизить качество (выбрать более высокое число, например 10).
Следующие строки инициализируют камеру с настройками, которые вы задали ранее. Если камера не инициализируется успешно, будет возвращено сообщение об ошибке.
// 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;
}
Инициализация WiFi
Функция initWiFi() подключает вашу плату к локальной сети, используя учётные данные, которые вы указали ранее.
// Connect to wifi
void initWiFi(){
WiFi.begin(ssid, password);
Serial.println("Connecting Wifi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
}
Инициализация времени и установка часового пояса
Функция setTimezone() устанавливает переменную окружения TZ с правильным часовым поясом. Вызов setenv("TZ", timezone, 1), где timezone — один из часовых поясов, перечисленных здесь. Затем вызывается tzset() для обновления часового пояса. Вы должны были определить свой часовой пояс ранее в коде (переменная myTimezone).
// Function to set timezone
void setTimezone(String timezone){
Serial.printf(" Setting Timezone to %s\n",timezone.c_str());
setenv("TZ",timezone.c_str(),1); // Now adjust the TZ. Clock settings are adjusted to show the new local time
tzset();
}
Функция initTime() подключает ESP32-CAM к NTP-серверу и устанавливает правильный часовой пояс.
// Connect to NTP server and adjust timezone
void initTime(String timezone){
struct tm timeinfo;
Serial.println("Setting up time");
configTime(0, 0, "pool.ntp.org"); // First connect to NTP server, with 0 TZ offset
if(!getLocalTime(&timeinfo)){
Serial.println(" Failed to obtain time");
return;
}
Serial.println("Got the time from NTP");
// Now we can set the real timezone
setTimezone(timezone);
}
Получение текущего времени и имени файла для фотографии
Вся цель этого урока — сохранять фотографии на карту microSD, включая дату и время в имени файла. Имя файла будет в следующем формате:
picture_YYYY-MM-DD_HH-MM-SS.jpg
Функция getPictureFilename() получает текущую дату и время и выводит строку, которую можно использовать как имя файла для фотографии.
String getPictureFilename(){
Сначала мы получаем дату и время:
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
return "";
}
Затем мы преобразуем переменную timeinfo в строку в формате Y-m-d_H-M-S и выводим результат:
strftime(timeString, sizeof(timeString), "%Y-%m-%d_%H-%M-%S", &timeinfo);
Serial.println(timeString);
Получив дату и время в формате строки, мы можем объединить их с «picture» и «.jpg» — форматом изображения.
String filename = "/picture_" + String(timeString) +".jpg";
Наконец, мы возвращаем имя файла:
return filename;
Инициализация карты microSD
Функция initMicroSDCard() инициализирует карту microSD. Она возвращает сообщение об ошибке, если инициализация не удалась.
// Start Micro SD card
Serial.println("Starting SD Card");
if(!SD_MMC.begin()){
Serial.println("SD Card Mount Failed");
return;
}
Следующие строки возвращают ошибку, если карта microSD не подключена.
uint8_t cardType = SD_MMC.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD Card attached");
return;
}
Съёмка и сохранение фотографии
Для съёмки и сохранения фотографии мы создали функцию takeSavePhoto().
Сначала она делает снимок с помощью функции esp_camera_fb_get().
camera_fb_t * fb = esp_camera_fb_get();
Эта строка создаёт буфер кадров fb, который содержит изображение.
Если вы получаете старые изображения при фотографировании, это может означать, что буфер изображений сохраняет более одной фотографии. Чтобы предотвратить это, вы можете раскомментировать следующие строки в коде:
//esp_camera_fb_return(fb); // dispose the buffered image
//fb = NULL; // reset to capture errors
//fb = esp_camera_fb_get();
Если захват не удался, мы выводим сообщение об ошибке и перезагружаем плату (перезагрузка платы необязательна, вы можете обработать ошибку иначе, например, попытаться сделать ещё один снимок).
if(!fb) {
Serial.println("Camera capture failed");
delay(1000);
ESP.restart();
}
После съёмки мы вызываем функцию getPictureFilename() для получения имени файла с текущей временной меткой.
// Path where new picture will be saved in SD Card
String path = getPictureFilename();
Serial.printf("Picture file name: %s\n", path.c_str());
Теперь, когда у нас есть изображение (буфер кадров fb) и имя файла, мы можем сохранить его на карту microSD.
Откройте карту microSD в режиме записи и создайте файл с именем, которое мы определили ранее (оно находится в переменной path):
fs::FS &fs = SD_MMC;
File file = fs.open(path.c_str(),FILE_WRITE);
Если мы не можем открыть файл (из-за ошибки карты microSD или при создании файла), мы выводим сообщение об ошибке.
if(!file){
Serial.printf("Failed to open file in writing mode");
}
Сохраните изображение, передав в качестве аргументов буфер кадров и его длину методу write().
file.write(fb->buf, fb->len); // payload (image), payload length
После этого закройте файл:
file.close();
Наконец, вызовите esp_camera_fb_return(fb) для очистки буфера fb, чтобы он был доступен для следующего снимка.
esp_camera_fb_return(fb);
setup()
В setup() инициализируйте Serial Monitor, инициализируйте Wi-Fi, установите часовой пояс, настройте и инициализируйте камеру, а также инициализируйте карту microSD.
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detector
Serial.begin(115200);
delay(2000);
// Initialize Wi-Fi
initWiFi();
// Initialize time with timezone
initTime(myTimezone);
// Initialize the camera
Serial.print("Initializing the camera module...");
configInitCamera();
Serial.println("Ok!");
// Initialize MicroSD
Serial.print("Initializing the MicroSD card module... ");
initMicroSDCard();
}
loop()
В loop() вызовите функцию takeSavePhoto() для съёмки и сохранения фотографии на карту microSD. Имя файла уже будет содержать текущую временную метку.
void loop() {
// Take and Save Photo
takeSavePhoto();
delay(10000);
}
Эта функция вызывается снова и снова в loop() каждые 10 секунд.
Мы делаем это в демонстрационных целях. Теперь вы сможете использовать функцию takeSavePhoto() в своих собственных проектах для съёмки фотографий с уникальным именем файла.
Демонстрация
После ввода ваших сетевых учётных данных и строки часового пояса загрузите код на вашу плату.
Если у вас ESP32-CAM AI-Thinker, вы должны выбрать AI Thinker ESP32-CAM в меню Tools > Board.
После этого выберите COM-порт в Tools > Port.
Наконец, загрузите код.
Если у вас возникли проблемы с загрузкой кода, ознакомьтесь с нашим руководством по устранению неполадок: ESP32-CAM Troubleshooting Guide: Most Common Problems Fixed.
После успешной загрузки кода на плату откройте Serial Monitor на скорости 115200 бод. Нажмите кнопку RST, чтобы перезагрузить плату и начать выполнение кода.
Не забудьте вставить карту microSD в слот карты microSD на ESP32-CAM.
Каждые 10 секунд вспышка будет включаться, и фотография будет сохраняться на карту microSD. Дайте коду поработать некоторое время, чтобы получить достаточное количество фотографий.
В Serial Monitor вы должны увидеть что-то вроде того, что показано ниже.
Затем вставьте карту microSD в ваш компьютер, чтобы просмотреть сделанные фотографии.
Фотографии должны иметь дату и время в названии. Например:
picture_2023-01-17_17-31-40.jpg
Это означает, что эта фотография была сделана:
Год: 2023
Месяц: 01 (январь)
День: 17-е
Время: 17:31:40
Заключение
В этом проекте вы узнали, как запрашивать дату и время (с правильным часовым поясом и летним временем) из интернета с помощью ESP32-CAM и использовать эту информацию для именования ваших фотографий. Таким образом, вы знаете, в какое время и дату была сделана определённая фотография, и вам не нужно беспокоиться о перезаписи фотографий на карте microSD.
Для подробного урока о получении даты и времени с ESP32 и настройке часового пояса мы рекомендуем следующий урок: