Watch videos on your smartphone [M5Stack TimerCamera]

In the Video

Japanese【日本語】

English【英語】

1. Overview

We will do electronic work to watch the video taken by the camera on the web browser of the smartphone.
The product to be used is the TimerCamera of M5Stack.
This product implements a web server and distribution server to realize video distribution.

2. The development environment

Arduino was developed in Italy under the philosophy of “making things easier and easier to understand”.
Currently, it is widely used for learning all over the world, and the library is also substantial.
So, if you want to start electronic work, I think this is the only development environment.
Therefore, I use Arduino at this time.

3. Software

All program files are put together in this zip file. Please download as needed.

Listed below are the 3 files of the Arduino program in this zip file.

//*************************************************************************
//  CameraWebAccess Ver2023.02.03
//  Arduino Board : M5Stack-Timer-CAM [M5Stack ver 2.0.6]
//  Written by IT-Taro
//***********************************************************************

#include <WiFi.h>
#include "esp_http_server.h"
#include "esp_camera.h"

// ################ for Battery Use###############
//#include "battery.h"
//#include "soc/rtc_cntl_reg.h" // for BrouwnoutDetector Disable

//#define BATTERY_ENABLE

// ####################### Wi-Fi settings (Preferences) #######################
// Wi-Fi settings
const char *ssid      = "##### SSID #####";
const char *password  = "### PASSWORD ###";

IPAddress ip(192, 168, 1, 123);     // IP address (IP used by this machine)
IPAddress gateway(192, 168, 1, 1);  // default gateway
IPAddress subnet(255, 255, 255, 0); // sub-net mask
IPAddress dns(192, 168, 1, 1);      // DNS server[Required setting: WiFiClientSecure cannot be used with a fixed IP]
// ###################################################################

// pin arrangement etc.
const byte LED_PIN       = 2;  // green LED
// CAMERA_MODEL_M5_UNIT_CAM
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     25
#define SIOC_GPIO_NUM     23

#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       32
#define VSYNC_GPIO_NUM    22
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

httpd_handle_t webServer = NULL;
httpd_handle_t streamServer = NULL;

void setup() {
  Serial.begin(115200);

#ifdef BATTERY_ENABLE
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable   detector
  bat_init();
  bat_hold_output();
#endif
  //Serial.setDebugOutput(true);
  //Serial.println();

  // ####### CAMERA initial settings #######
  camera_config_t config;
  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;
  // Image size setting:QVGA(320x240),CIF(400x296),HVGA(480x320),VGA(640x480),SVGA(800x600),XGA(1024x768)
  config.frame_size = FRAMESIZE_SVGA;   // HTML needs to be sized
  config.jpeg_quality = 10;
  config.fb_count = 1;
  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  /*sensor_t *s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  s->set_vflip(s, 1);       // flip it back
  s->set_brightness(s, 1);  // up the blightness just a bit
  s->set_saturation(s, -2); // lower the saturation
  // drop down frame size for higher initial frame rate
  s->set_framesize(s, FRAMESIZE_QVGA);*/

  // ####### PIN setting start #######
  pinMode ( LED_PIN, OUTPUT );

  // ####### Wireless Wi-Fi connection #######
  bool ledFlag = true;
  WiFi.config( ip, gateway, subnet, dns );
  WiFi.begin ( ssid, password );
  while ( WiFi.status() != WL_CONNECTED ) { // Infinite loop processing until connected
    // LED flashes every second while connected
    ledFlag = !ledFlag;
    digitalWrite(LED_PIN, ledFlag);
    delay ( 1000 );
    Serial.print ( "." );
  }
  // Wi-Fi connection completed (IP address display)
  Serial.print ( "Wi-Fi Connected! IP address: " );
  Serial.println ( WiFi.localIP() );
  // LED lights when Wi-Fi is connected (Wi-Fi connection status)
  digitalWrite ( LED_PIN, true );

  // ####### HTTP Server settings and Start #######
  stratHttpServer();

  Serial.println("Setup Finished!");
}

void loop() {
  delay(1);
}
void stratHttpServer(){
    // webServer
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    //config.max_uri_handlers = 16; // 8 or more must be set

    httpd_uri_t index_uri = {
        .uri       = "/",
        .method    = HTTP_GET,
        .handler   = index_handler,
        .user_ctx  = NULL};
    
    httpd_uri_t capture_uri = {
        .uri = "/capture",
        .method = HTTP_GET,
        .handler = capture_handler,
        .user_ctx = NULL};
    
    httpd_uri_t reset_uri = {
        .uri = "/reset",
        .method = HTTP_GET,
        .handler = reset_handler,
        .user_ctx = NULL};

    httpd_uri_t stream_uri = {
        .uri       = "/stream",
        .method    = HTTP_GET,
        .handler   = stream_handler,
        .user_ctx  = NULL};

    // Start the web(httpd) server
    if (httpd_start(&webServer, &config) == ESP_OK) {
        httpd_register_uri_handler(webServer, &index_uri);
        httpd_register_uri_handler(webServer, &capture_uri);
        httpd_register_uri_handler(webServer, &reset_uri);
    }

    // StremServer
    config.server_port += 1;
    config.ctrl_port += 1;
    // Start the stream server
    if (httpd_start(&streamServer, &config) == ESP_OK) {
        /* Register URI handlers */
        httpd_register_uri_handler(streamServer, &stream_uri);
    }
}

static esp_err_t stream_handler( httpd_req_t *req ) {
    #define PART_BOUNDARY "123456789000000000000987654321"
    char strbuf[128];
    esp_err_t res = ESP_OK;
    camera_fb_t *fb = NULL;
    static const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
    static const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";

    Serial.println( "Start Stream!" );
    // Send first reply packet
    res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
    if (res != ESP_OK) {
        return res;
    }

    // Initial response packet header setting when sending image data (loop)
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
    httpd_resp_set_hdr(req, "X-Framerate", "60");
    // Repeat image transmission
    while (true) {
        // Get camera JPEG
        fb = esp_camera_fb_get();
        if (!fb) {
            Serial.println( "Camera capture failed" );
            res = ESP_FAIL;
            break;
        }
        // send image separator
        if (res == ESP_OK) {
            res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
        }
        // send image header
        if (res == ESP_OK) {
            size_t hlen = snprintf((char *)strbuf, 128, "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n",
                fb->len, fb->timestamp.tv_sec, fb->timestamp.tv_usec);
            res = httpd_resp_send_chunk(req, (const char *)strbuf, hlen);
        }
        // Image JPEG data transmission
        if (res == ESP_OK) {
            res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len);
        }
        // Camera termination processing
        if (fb) {
            esp_camera_fb_return(fb);
            fb = NULL;
        }
        // Exit loop if response is unsuccessful
        if (res != ESP_OK) {
            Serial.println( "Stop Stream!" );
            break;
        }
    }
    return res;
}

static esp_err_t capture_handler( httpd_req_t *req ) {
    camera_fb_t *fb = NULL;
    esp_err_t res = ESP_OK;
    Serial.println( "Start Capture!" );
    // Get camera JPEG
    fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println( "Camera capture failed" );
        return ESP_FAIL;
    }
    httpd_resp_set_type(req, "image/jpeg");
    httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
    httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
    res = httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len);
    // camera end processing
    if (fb) {
        esp_camera_fb_return(fb);
        fb = NULL;
    }
    Serial.println( "Finish Capture!" );
    return res;
}

static esp_err_t reset_handler( httpd_req_t *req ) {
    Serial.println ( "Reset" );
    char *resMsg = "OK Reboot!";
    httpd_resp_send(req, resMsg, strlen(resMsg) );
    delay(1000);
    ESP.restart();
}
// HTML top response processing
static esp_err_t index_handler( httpd_req_t *req ) {
  // response HTML definition
  char *htmltop = 
  "<!DOCTYPE html><html lang=\"jp\"><head><meta charset=\"UTF-8\"/>\r\n"
  "<style type=\"text/css\"><!--\r\n"
  "#contents { max-width: 800px; }\r\n"
  "img { width:100%;height:600px; background-color:grey; }\r\n"
  "h1 { margin: 0px; font-size: 36px; }\r\n"
  "button { width:150px;height:50px; font-size: 24px; }\r\n"
  "footer { text-align: right; }\r\n"
  ".underTheEarthKai { background-image: radial-gradient(50% 150%, #CCCCCC 5%, #777777 100%); }\r\n"
  "background-image: linear-gradient(-173deg, rgba(255,255,255,0.20) 0%, #000000 100%),\r\n"
  "linear-gradient(72deg, rgba(255,255,255,0.25) 25%, rgba(0,0,0,0.25) 100%),\r\n"
  "radial-gradient(47% 102%, rgba(255,255,255,0.50) 0%, rgba(21,24,32,0.60) 120%);background-blend-mode: multiply; }\r\n"
  "#msg { vertical-align:middle; }\r\n"
  ".floatleft { float:left; }\r\n"
  ".floatright { float:right; }\r\n"
  "--></style>\r\n"
  "<title>M5Stack TimerCamera</title><link rel=\"shortcut icon\" href=\"https://hobby-it.com/favicon.ico\"></head>\r\n"
  "<body class=\"underTheEarthKai\"><center><div id=\"contents\"><header><h1>Web Camera [M5Stack TimerCamera]</h1></header>\r\n"
  "<div class=\"floatright\"><button id=\"capbtn\" type=\"button\" onclick=\"window.open('/reset')\">REBOOT</button></div>\r\n"
  "<form id=\"canform\" method=\"get\" action=\"javascript:void(0);\">\r\n"
  "<div id=\"contentImg\"><img id=\"live\"></div>\r\n"
  "<div><div class=\"floatleft\">\r\n"
  "<button id=\"startbtn\" onclick=\"wsConnect()\">START</button>\r\n"
  "<button id=\"endbtn\" onclick=\"wsDisconnect()\">STOP</button>\r\n"
  "</div><div class=\"floatright\">\r\n"
  "<button id=\"clearbtn\" onclick=\"clearimg()\">CLEAR</button>\r\n"
  "<button id=\"capbtn\" onclick=\"capture()\">ONE-SHOT</button>\r\n"
  "</div></div><br><br><br>\r\n"
  "<div><font color=\"red\" size=+3><span id=\"msg\">Please press the button</span></font></div>\r\n"
  "<footer>©Hobby-IT</footer></form></div></center>\r\n"
  "\r\n"
  "<script language=\"javascript\" type=\"text/javascript\">\r\n"
  "function dispMessage(message){ document.getElementById('msg').innerHTML = message;}\r\n"
  "function wsConnect(){\r\n"
  " document.getElementById(\"startbtn\").disabled = true;\r\n"
  " var baseHost = document.location.origin;\r\n"
  " var streamUrl = baseHost + ':81/stream';\r\n"
  " document.getElementById('live').src = streamUrl;\r\n"
  " dispMessage('Delivery started');\r\n"
  "}\r\n"
  "function wsDisconnect(){\r\n"
  " window.stop();\r\n"
  " document.getElementById(\"startbtn\").disabled = false;\r\n"
  " dispMessage('Delivery finished');\r\n"
  "}\r\n"
  "function clearimg(){\r\n"
  " document.getElementById('live').src='';\r\n"
  " document.getElementById('live').remove();\r\n"
  " var imgTag = document.createElement('img');\r\n"
  " imgTag.id = \"live\";\r\n"
  " document.getElementById('contentImg').appendChild(imgTag);\r\n"
  " dispMessage('Cleared');\r\n"
  "}\r\n"
  "function capture(){\r\n"
  " var baseHost = document.location.origin;\r\n"
  " var captureUrl = baseHost + '/capture';\r\n"
  " document.getElementById('live').src = captureUrl;\r\n"
  " dispMessage('I took a picture');\r\n"
  "}\r\n"
  "</script></body></html>\r\n";

  // Send response HTML
  Serial.println("send htmltop");
  return httpd_resp_send(req, htmltop, strlen(htmltop));
}

4. PartsList

As a web camera for electronic work, it compares the prices of items used.

Document [at youtube]

Comments

Copied title and URL