3 Mar 2021

Membuat Arduino Uno Bluetooth Sendiri Bisa Upload Sketch Lewat Bluetooth | DIY Bluino One

Halo agan semuanya, sekarang ane mau nunjukin kepada agan bagaimana membuat papan arduino uno sendiri yang dapat mengunggah kode/sketch melalui bluetooth HC-05, sistem minimum berbasis arduino ini bisa disebut "DIY Bluino One".

Jadi dalam tutorial ini agan bisa melihat gambar rangkaian DIY Bluino One, menyetak PCB di PCBWay, menyolder komponen dan menyeting modul bluetooth HC-05. Disisi agan akan menggunakan 2 komponen SMD (AMS117 3V3 / 5V), karena tidak semua dapat menyolder banyak komponen SMD dengan mudah. Selain itu, komponen bukan jenis SMD biasanya harganya lebih murah.

Saya juga telah buat video detail langkah demi langkah seluruh prosesnya di youtube, jadi jika agan tidak ingin membaca seluruh tutorial ini, tonton video nya saja.

Ikutin tutorial ini nanti agan bakal ditunjukin alat-alat dan komponen apa saja yang diperlukan serta bagaimana cara membuatnya.

Artikel ini dengan bangga disponsori oleh PCBWAY. Jika agan ingin membuat PCB dan sedang mencari jasa cetak PCB, bisa langsung order saja ke PCBWAY.COM, harganya lebih murah $5 bisa mendapatkan papan PCB 10 keping dengan kualitas PCB yang sangat bagus.

Daftar Komponen:

Daftar Komponen:

  • 1 x PCB DIY Bluino One (PCBWAY)
  • 1 x Bluetooth HC-05 module
  • 1 x USB to TTL CP2102 Module
  • 1 x IC Atmega328P DIP
  • 1 x IC Socket 28 Pin
  • 1 x Voltage Regulator AMS1117 3V3
  • 1 x Voltage Regulator AMS1117 5V
  • 1 x Crystal 16MHz
  • 1 x Diode 1N4001
  • 1 x LED Red 3mm
  • 1 x LED Yellow 3mm
  • 2 x Resistor 1K Ohm
  • 1 x Resistor 10K Ohm
  • 1 x Resistor 20K Ohm
  • 2 x Capacitor 100uF/16V
  • 4 x Capacitor 0.1uF (104)
  • 2 x Capacitor 22pF (22)
  • 1 x DC Barrel Socket 5.5mm
  • 1 x Tact Switch 6x6mm
  • 1 x SPDT Slide Switch
  • 1 x 10 Pin Male Header
  • 2 x 8 Pin Male Header
  • 2 x 6 Pin Male Header
  • 2 x 3 Pin Male Header

  • Order PCB (PCBWAY)

    Untuk membuat proyek ini kamu perlu memesan prototipe PCB di PCBWAY. Cara pemesanannya sangat mudah, kamu akan mendapatkan 10 keping PCB seharga $5 dengan kualitas PCB yang sangat bagus.

    Langkah-langkah order:

    1. SignUp/Log in di pcbway.com

    2. Buka tautan proyek PCB ini.

    3. Klik Tambahkan ke troli.

    4. Tunggu sebentar untuk review PCB, lalu Klik Check Out.

    Menyolder Komponen SMD

    Proyek kali ini memiliki dua komponen SMD, jangan khawatir komponen ini tidak terlalu sulit untuk disolder karena ukurannya cukup besar, agan hanya membutuhkan pinset sebagai alat bantu untuk menahan komponen saat disolder. Langkah pertama beri lapisan timah pada tembaga PCB untuk tempat kaki komponen SMD.

    Kemudian ditahan menggunakan pinset lalu di panaskan kembali timah hingga menempel dengan kaki komponen SMD.

    Memasang Komponen

    Pasang semua komponen pada PCB mengikuti gambar/simbol komponen yang ada pada bagian atas PCB, untuk lebih jelasnya dapat mengikuti step by step pada video youtube.

    Menyolder Komponen

    Solder semua kaki komponen pada bagian bawah PCB, untuk lebih jelasnya dapat mengikuti step by step pada video youtube.

    Kemudian potong kaki komponen yang panjang menggunakan tang pemotong, hati-hati ketika memotong pastikan memakai kacamata safety.

    Menseting Parameter pada Bluetooth HC-05

    Pada dasarnya dalam langkah ini adalah cara mengubah parameter pada HC-05 melalui AT Command, langkah ini sama dengan tutorial sebelumnya di Instructables disini. Modul Bluetooth HC-05 yang bisa digunakan pada project ini adalah yang memiliki versi firmware 2.0 atau 3.0, karena HC-05 dengan versi firmware 4.0 tidak memiliki parameter POLAR untuk dirubah.

    Dengan mengikuti step dibawah ini agan akan menseting beberapa parameter HC-05:

    1. Hubungkan bluetooth HC-05 dengan USB ke modul TTL CP2102 mengikuti gambar rangkaian berikut
    2. Press and hold button on HC-05 while you powered the CP2102 (to enter AT+Command mode)
    3. Open Serial Monitor on Arduino IDE or other Serial Software
    4. Set baudrate to 38400 and set end of send string with "Both NL & CR" (New Line & Carriage Return)
    5. Kirim text di bawah untuk merubah parameter HC-05:
      • AT+NAME=XXXX
      • AT+UART=115200,0,0
      • AT+POLAR=1,0

    Menyolder bluetooth HC-05 ke PCB

    Setelah selesai merubah parameter pada modul bluetooth HC-05. Selanjutnya lepas kondom plastik pada modul bluetooth HC-05. Kemudian potong kaki male pin header pada sisi yang panjangnya (lihat gambar) atau ikuti langkah pada video

    Pasangkan modul bluetooth HC-05 pada bagian atas PCB, bisa juga bantu direkatkan menggunakan double tape.

    Terakhir solder pada bagian belakang PCB

    Memasang IC Atemega328 dan modul CP2102

    IC Atemega328 yang digunakan harus memiliki boot-loader Arduino Uno didalamnya, agan bisa mengisi boot-loader ini dengan menggunakan Arduino lain sebagai alatnya untuk caranya bisa googling saja, tapi jika agan gak mau ribet disarankan beli aja IC Atmega328 yang sudah terisi boot-loader Arduino uno didalamnya harganya murah tidak jauh beda dengan IC Atmega yang kosong, banyak dijual di tokopedia.

    Dalam proyek ini juga menggunakan modul USB to TTL CP2102, agan juga dapat menggunakan modul USB to TTL jenis lain seperti FTDI232, hanya saja tidak akan bisa dipasang langsung ke soket di PCB, agan perlu kabel jumper untuk menghubungkan antar pin yang sesuai.

    Selesai: Coba Upload Sketch Lewat Bluetooth

    Sudah sampai langkah ini berarti agan sudah selesai merakit hardware "DIY Bluino One" (Arduino Uno Bluetooth). Nah sekarang saatnya mencoba upload sketch pertama ke hardware, cara upload sketch nya bisa menggunakan software Arduino IDE di komputer cara upload bisa melalui bluetooth atau USB. Cara lain agan dapat upload sketch menggunakan smartphone Android bisa melalui Bluetooth atau USB OTG. Install aplikasi Bluino Loader di Google Playstore.

    18 Jan 2021

    Membuat Robot Mobil RC Kamera Menggunakan ESP32 Cam

    Halo agan agan sekalian, kalau pernah punya mobil-mobilan remote control alias RC sekarng mungkin sudah biasa, tapi kalau mobil RC nya ada kamera kayanya sedikit luar biasa. Apalagi kalau agan sendiri yang buatnya tambah luar biasa.

    Ikutin tutorial ini nanti agan bakal ditunjukin alat-alat dan komponen apa saja yang diperlukan serta bagaimana cara membuatnya.

    Jika agan ingin membuat PCB dan sedang mencari jasa cetak PCB, bisa langsung order saja ke PCBWAY.COM, harganya lebih murah $5 bisa mendapatkan papan PCB 10 keping dengan kualitas PCB yang sangat bagus.

    Daftar Komponen:

  • 1 x PCB ESP32-Cam Motor Shield (PCBWAY)
  • 1 x ESP32-Cam Board
  • 1 x USB to TTL CP2102 Module
  • 1 x IC L293D Driver Motor
  • 2 x Transistor NPN BC547
  • 1 x Voltage Regulator 5V AMS1117
  • 1 x Diode 1N4001
  • 1 x LED Red 3mm
  • 5 x Resistor 10K Ohm
  • 2 x Resistor 1K Ohm
  • 1 x Capacitor 100uF/16V
  • 1 x Active Buzzer 5V
  • 1 x Tact Switch
  • 1 x SPDT Slide Switch
  • 3 x 2 Pin Terminal Block Screw
  • 4 x 10 pin Male Header
  • 1 x 8 pin Female Header
  • 1 x 6 pin Female Header
  • 2 x 8 pin Long Legs Female Header

  • Order PCB (PCBWAY)

    Untuk membuat proyek ini kamu perlu memesan prototipe PCB di PCBWAY. Cara pemesanannya sangat mudah, kamu akan mendapatkan 10 keping PCB seharga $5 dengan kualitas PCB yang sangat bagus.

    Langkah-langkah order:

    1. SignUp/Log in di pcbway.com

    2. Buka tautan proyek PCB ini.

    3. Klik Tambahkan ke troli.

    4. Tunggu sebentar untuk review PCB, lalu Klik Check Out.

    Menyolder Komponen SMD

    Proyek kali ini memiliki satu komponen SMD, jangan khawatir komponen ini tidak terlalu sulit untuk disolder karena ukurannya cukup besar, agan hanya membutuhkan pinset sebagai alat bantu untuk menahan komponen saat disolder. Langkah pertama beri lapisan timah pada tembaga PCB untuk tempat kaki komponen SMD.

    Kemudian ditahan menggunakan pinset lalu di panaskan kembali timah hingga menempel dengan kaki komponen SMD.

    Memasang Komponen

    Pasang semua komponen pada PCB mengikuti gambar/simbol komponen yang ada pada bagian atas PCB, untuk lebih jelasnya dapat mengikuti step by step pada video youtube.

    Menyolder Komponen

    Solder semua kaki komponen pada bagian bawah PCB, untuk lebih jelasnya dapat mengikuti step by step pada video youtube.

    Kemudian potong kaki komponen yang panjang menggunakan tang pemotong, hati-hati ketika memotong pastikan memakai kacamata safety.

    Memasang Socket ESP32-Cam

    Terdapat dua jenis female pin header 8 pin yang akan digunakan, yaitu dengan kaki yang pendek dan kaki yang panjang.
    Untuk kaki yang pendek bengkokan semua kaki pin menggunakan tang hingga membentuk sudut 90 derajat.

    Kemudian pasang pada PCB bagian depan seperti terlihat pada gambar.

    Langkah selanjutnya solder pada bagian belakang PCB.

    Jenis yang kedua female pin header 8 pin dengan kaki yang panjang, disini menggunakan sebanyak 2 buah.
    Pertama bengkokan terlebih dahulu salah satu female pin header.

    Setelah itu sambungkan dengan cara memasukan ke female pin header yang satunya lagi dan jangan terlalu dalam. Pastikan setiap pin tersambung dengan menggunakan multitester untuk mengetes continuity.
    Setelah itu lem menggunakan power glue supaya tidak mudah terlepas.

    Dengan bantuan ESP32-Cam board sebagai mal pasangkan pada PCB.

    Langkah terakhir solder pada bagian belakang PCB.

    Memasang IC, ESP32-Cam & USB to TTL Modul

    Pasang IC L293D pada soket IC, lihat posisinya jangan terbalik. Kemudian pasang modul USB to TTL CP2102 yang sudah memiliki pin header.

    Memasang ESP32-Cam Motor Shield ke Chassis

    Agan bisa menggunakan chasis custom untuk mobil robot yang banyak dipasaran bisa didapat dari toko online yang biasa digunakan untuk membangun robot manggunakan Arduino Uno, karena ukuran PCB dan jarak lubang sama dengan Arduino Uno.
    Untuk jumlah roda/motor penggerak tidak masalah jika agan menggunakan empat atau dua motor, sebenarnya output dari ESP32-Cam Motor Shield motor hanya dua yaitu kiri dan kanan, kalau ingin menggunakan empat roda/motor penggerak hanya dipasang secara paralel, berikut gambar rangkaiannya.

    Hubungkan motor kiri dan motor kanan ke terminal sekrup pada output driver motor ESP32-Cam Motor Shield. Hubungkan baterai 2 x 18650 3.7V ke sekrup terminal catu daya pada ESP32-Cam Motor Shield.

    Mengisi Program ke ESP32-Cam Board

    Untuk membuat WiFi Kamera RC Car berfungsi dan dapat dikendalikan oleh smartphone Android, agan harus memprogram ESP32-Cam terlebih dahulu. Caranya cukup mudah hanya cukup menggunakan smartphone Android tidak memerlukan lagi Laptop/PC, ikuti langkah berikut:

    - Install ESP32 Camera WiFi Robot Car dari Google Playstore.

    - Hubungkan antara smartphone Android dengan ESP32-Cam Motor Shield menggunakan kabel USB dan USB OTG adapter. (pastikan HP agan support USB OTG)

    - Untuk menjadikan ESP32-Cam board siap untuk diisi program, dalam kondisi menyala agan perlu menekan serta menahan tombol GPIO0 kemudian menekan tombol reset pada ESP32-Cam board. setelah itu lepas kan keduanya maka sekarang posisi chip ESP32 siap untuk diisi program melalui USB to TTL CP2102.

    - Buka aplikasi ESP32 Camera Wifi Robot Car, pada menu bar atas tekan icon Circuit diagram & Code. Kemudian setelah terbuka tampilan gambar rangkaian dan kode pada menu bar atasnya tekan ikon tiga titik (menu lainnya), buka pilihan Motor Driver to be Used lalu pilih ESP32-Cam Motor Shield, kemudian buka pilihan Upload Firmware Via lalu pilih USB OTG, jika ingin menghubungkan Mobil RC ke sebuah router jaringan rumah agan bisa set SSID name & Password. Setelah selesai konfigurasi langkah selanjutnya tekan ikon UPLOAD, dan tunggu sampai proses uploadnya selesai. Terakhir tekan tombol reset untuk menjalankan program.

    Selain mengisi program melalui smartphone Android, agan dapat juga memprogram ESP32-Cam melalui komputer menggunakan software Arduino IDE menggunakan kode program dibawah ini:

    Catatan: Software Arduino IDE agan harus sudah disetting untuk dapat digunakan untuk memprogram ESP32 sebelumnya.

    Simpan sketch di bawah dengan nama file "esp32cam_wifi_robot_car_l293d.ino"

    
    #include "esp_camera.h"
    #include <WiFi.h>
    #include <ArduinoOTA.h>
    
    /* Wifi Crdentials */
    String sta_ssid = "$your_ssid_maximum_32_characters";     // set Wifi network you want to connect to
    String sta_password = "$your_pswd_maximum_32_characters";   // set password for Wifi network
    
    /* define 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
    
    /* Defining motor and servo pins */
    extern int DRV_A = 12;
    extern int DRV_B = 13;
    extern int DIR_A = 14;
    extern int DIR_B = 15;
    
    extern int ledVal = 20;  // setting bright of flash LED 0-255
    
    extern int ledPin = 4;  // set digital pin GPIO4 as LED pin (use biult-in LED)
    extern int buzzerPin = 2;  // set digital pin GPIO2 as LED pin (use Active Buzzer)
    extern int servoPin = 2;  // set digital pin GPIO2 as servo pin (use SG90)
    
    unsigned long previousMillis = 0;
    
    void startCameraServer();
    
    void initServo() {
      ledcSetup(8, 50, 16); /*50 hz PWM, 16-bit resolution and range from 3250 to 6500 */
      ledcAttachPin(servoPin, 8);
    }
    
    void initLed() {
      ledcSetup(7, 5000, 8); /* 5000 hz PWM, 8-bit resolution and range from 0 to 255 */
      ledcAttachPin(ledPin, 7);
    }
    
    void setup() {
      Serial.begin(115200);         // set up seriamonitor at 115200 bps
      Serial.setDebugOutput(true);
      Serial.println();
      Serial.println("*ESP32 Camera Remote Control - L293D Bluino Shield*");
      Serial.println("--------------------------------------------------------");
    
      // Set all the motor control pin to Output
      pinMode(DRV_A, OUTPUT);
      pinMode(DRV_B, OUTPUT);
      pinMode(DIR_A, OUTPUT);
      pinMode(DIR_B, OUTPUT);
      
      pinMode(ledPin, OUTPUT); // set the LED pin as an Output
      pinMode(buzzerPin, OUTPUT); // set the buzzer pin as an Output
      pinMode(servoPin, OUTPUT); // set the servo pin as an Output
    
      // Initial state - turn off motors, LED & buzzer
      digitalWrite(DRV_A, LOW);
      digitalWrite(DRV_B, LOW);
      digitalWrite(DIR_A, LOW);
      digitalWrite(DIR_B, LOW);
      digitalWrite(ledPin, LOW);
      digitalWrite(buzzerPin, LOW);
      digitalWrite(servoPin, LOW);
    
      /* Initializing Servo and LED */
      initServo();
      initLed();
      
      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;
      //init with high specs to pre-allocate larger buffers
      if(psramFound()){
        config.frame_size = FRAMESIZE_UXGA;
        config.jpeg_quality = 10;
        config.fb_count = 2;
      } else {
        config.frame_size = FRAMESIZE_SVGA;
        config.jpeg_quality = 12;
        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;
      }
    
      //drop down frame size for higher initial frame rate
      sensor_t * s = esp_camera_sensor_get();
      s->set_framesize(s, FRAMESIZE_QVGA);
    
      // Set NodeMCU Wifi hostname based on chip mac address
      char chip_id[15];
      snprintf(chip_id, 15, "%04X", (uint16_t)(ESP.getEfuseMac()>>32));
      String hostname = "esp32cam-" + String(chip_id);
    
      Serial.println();
      Serial.println("Hostname: "+hostname);
    
      // first, set NodeMCU as STA mode to connect with a Wifi network
      WiFi.mode(WIFI_STA);
      WiFi.begin(sta_ssid.c_str(), sta_password.c_str());
      Serial.println("");
      Serial.print("Connecting to: ");
      Serial.println(sta_ssid);
      Serial.print("Password: ");
      Serial.println(sta_password);
    
      // try to connect with Wifi network about 10 seconds
      unsigned long currentMillis = millis();
      previousMillis = currentMillis;
      while (WiFi.status() != WL_CONNECTED && currentMillis - previousMillis <= 10000) {
        delay(500);
        Serial.print(".");
        currentMillis = millis();
      }
    
      // if failed to connect with Wifi network set NodeMCU as AP mode
      IPAddress myIP;
      if (WiFi.status() == WL_CONNECTED) {
        Serial.println("");
        Serial.println("*WiFi-STA-Mode*");
        Serial.print("IP: ");
        myIP=WiFi.localIP();
        Serial.println(myIP);
        delay(2000);
      } else {
        WiFi.mode(WIFI_AP);
        WiFi.softAP(hostname.c_str());
        myIP = WiFi.softAPIP();
        Serial.println("");
        Serial.println("WiFi failed connected to " + sta_ssid);
        Serial.println("");
        Serial.println("*WiFi-AP-Mode*");
        Serial.print("AP IP address: ");
        Serial.println(myIP);
        delay(2000);
      }
    
      // Start camera server to get realtime view
      startCameraServer();
      Serial.print("Camera Ready! Use 'http://");
      Serial.print(myIP);
      Serial.println("' to connect ");
    
      ArduinoOTA.begin();   // enable to receive update/upload firmware via Wifi OTA
    }
    
    void loop() {
      // put your main code here, to run repeatedly:
      ArduinoOTA.handle();
    }
    

    Simpan sketch di bawah dengan nama file "app_httpd.cpp"

    
    #include "esp_http_server.h"
    #include "esp_timer.h"
    #include "esp_camera.h"
    #include "img_converters.h"
    #include "Arduino.h"
    
    /* Initializing pins */
    extern int DRV_A;
    extern int DRV_B;
    extern int DIR_A;
    extern int DIR_B;
    extern int ledPin;
    extern int buzzerPin;
    extern int servoPin;
    extern int ledVal;
    
    typedef struct {
            httpd_req_t *req;
            size_t len;
    } jpg_chunking_t;
    
    #define PART_BOUNDARY "123456789000000000000987654321"
    static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
    static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
    static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
    
    httpd_handle_t stream_httpd = NULL;
    httpd_handle_t camera_httpd = NULL;
    
    
    static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){
        jpg_chunking_t *j = (jpg_chunking_t *)arg;
        if(!index){
            j->len = 0;
        }
        if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){
            return 0;
        }
        j->len += len;
        return len;
    }
    
    static esp_err_t capture_handler(httpd_req_t *req){
        camera_fb_t * fb = NULL;
        esp_err_t res = ESP_OK;
        int64_t fr_start = esp_timer_get_time();
    
        fb = esp_camera_fb_get();
        if (!fb) {
            Serial.printf("Camera capture failed");
            httpd_resp_send_500(req);
            return ESP_FAIL;
        }
    
        httpd_resp_set_type(req, "image/jpeg");
        httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
    
        size_t fb_len = 0;
        if(fb->format == PIXFORMAT_JPEG){
            fb_len = fb->len;
            res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
        } else {
            jpg_chunking_t jchunk = {req, 0};
            res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL;
            httpd_resp_send_chunk(req, NULL, 0);
            fb_len = jchunk.len;
        }
        esp_camera_fb_return(fb);
        int64_t fr_end = esp_timer_get_time();
        Serial.printf("JPG: %uB %ums", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000));
        return res;
    }
    
    static esp_err_t stream_handler(httpd_req_t *req){
        camera_fb_t * fb = NULL;
        esp_err_t res = ESP_OK;
        size_t _jpg_buf_len = 0;
        uint8_t * _jpg_buf = NULL;
        char * part_buf[64];
    
        static int64_t last_frame = 0;
        if(!last_frame) {
            last_frame = esp_timer_get_time();
        }
    
        res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
        if(res != ESP_OK){
            return res;
        }
    
        while(true){
            fb = esp_camera_fb_get();
            if (!fb) {
                Serial.printf("Camera capture failed");
                res = ESP_FAIL;
            } else {
                if(fb->format != PIXFORMAT_JPEG){
                    bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
                    esp_camera_fb_return(fb);
                    fb = NULL;
                    if(!jpeg_converted){
                        Serial.printf("JPEG compression failed");
                        res = ESP_FAIL;
                    }
                } else {
                    _jpg_buf_len = fb->len;
                    _jpg_buf = fb->buf;
                }
            }
            if(res == ESP_OK){
                size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
                res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
            }
            if(res == ESP_OK){
                res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
            }
            if(res == ESP_OK){
                res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
            }
            if(fb){
                esp_camera_fb_return(fb);
                fb = NULL;
                _jpg_buf = NULL;
            } else if(_jpg_buf){
                free(_jpg_buf);
                _jpg_buf = NULL;
            }
            if(res != ESP_OK){
                break;
            }
            int64_t fr_end = esp_timer_get_time();
    
            int64_t frame_time = fr_end - last_frame;
            last_frame = fr_end;
            frame_time /= 1000;
        }
    
        last_frame = 0;
        return res;
    }
    
    
    static esp_err_t cmd_handler(httpd_req_t *req){
        char*  buf;
        size_t buf_len;
        char variable[32] = {0,};
        char value[32] = {0,};
    
        buf_len = httpd_req_get_url_query_len(req) + 1;
        if (buf_len > 1) {
            buf = (char*)malloc(buf_len);
            Serial.println(buf);
            if(!buf){
                httpd_resp_send_500(req);
                return ESP_FAIL;
            }
            if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
                if (httpd_query_key_value(buf, "var", variable, sizeof(variable)) == ESP_OK &&
                    httpd_query_key_value(buf, "val", value, sizeof(value)) == ESP_OK) {
                } else {
                    free(buf);
                    Serial.println(buf);
                    httpd_resp_send_404(req);
                    return ESP_FAIL;
                }
            } else {
                free(buf);
                Serial.println(buf);
                httpd_resp_send_404(req);
                return ESP_FAIL;
            }
            Serial.println(buf);
            free(buf);
        } else {
            httpd_resp_send_404(req);
            Serial.println(ESP_FAIL);
            return ESP_FAIL;
        }
    
        int val = atoi(value);
        sensor_t * s = esp_camera_sensor_get();
        int res = 0;
    
        if(!strcmp(variable, "framesize")) {
            if(s->pixformat == PIXFORMAT_JPEG) res = s->set_framesize(s, (framesize_t)val);
        }
        else if(!strcmp(variable, "quality")) res = s->set_quality(s, val);
        else if(!strcmp(variable, "contrast")) res = s->set_contrast(s, val);
        else if(!strcmp(variable, "brightness")) res = s->set_brightness(s, val);
        else if(!strcmp(variable, "saturation")) res = s->set_saturation(s, val);
        else if(!strcmp(variable, "gainceiling")) res = s->set_gainceiling(s, (gainceiling_t)val);
        else if(!strcmp(variable, "colorbar")) res = s->set_colorbar(s, val);
        else if(!strcmp(variable, "awb")) res = s->set_whitebal(s, val);
        else if(!strcmp(variable, "agc")) res = s->set_gain_ctrl(s, val);
        else if(!strcmp(variable, "aec")) res = s->set_exposure_ctrl(s, val);
        else if(!strcmp(variable, "hmirror")) res = s->set_hmirror(s, val);
        else if(!strcmp(variable, "vflip")) res = s->set_vflip(s, val);
        else if(!strcmp(variable, "awb_gain")) res = s->set_awb_gain(s, val);
        else if(!strcmp(variable, "agc_gain")) res = s->set_agc_gain(s, val);
        else if(!strcmp(variable, "aec_value")) res = s->set_aec_value(s, val);
        else if(!strcmp(variable, "aec2")) res = s->set_aec2(s, val);
        else if(!strcmp(variable, "dcw")) res = s->set_dcw(s, val);
        else if(!strcmp(variable, "bpc")) res = s->set_bpc(s, val);
        else if(!strcmp(variable, "wpc")) res = s->set_wpc(s, val);
        else if(!strcmp(variable, "raw_gma")) res = s->set_raw_gma(s, val);
        else if(!strcmp(variable, "lenc")) res = s->set_lenc(s, val);
        else if(!strcmp(variable, "special_effect")) res = s->set_special_effect(s, val);
        else if(!strcmp(variable, "wb_mode")) res = s->set_wb_mode(s, val);
        else if(!strcmp(variable, "ae_level")) res = s->set_ae_level(s, val);
        else {
            res = -1;
        }
    
        if(res){
            return httpd_resp_send_500(req);
        }
    
        httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
        return httpd_resp_send(req, NULL, 0);
    }
    
    static esp_err_t status_handler(httpd_req_t *req){
        static char json_response[1024];
    
        sensor_t * s = esp_camera_sensor_get();
        char * p = json_response;
        *p++ = '{';
    
        p+=sprintf(p, "\"framesize\":%u,", s->status.framesize);
        p+=sprintf(p, "\"quality\":%u,", s->status.quality);
        p+=sprintf(p, "\"brightness\":%d,", s->status.brightness);
        p+=sprintf(p, "\"contrast\":%d,", s->status.contrast);
        p+=sprintf(p, "\"saturation\":%d,", s->status.saturation);
        p+=sprintf(p, "\"special_effect\":%u,", s->status.special_effect);
        p+=sprintf(p, "\"wb_mode\":%u,", s->status.wb_mode);
        p+=sprintf(p, "\"awb\":%u,", s->status.awb);
        p+=sprintf(p, "\"awb_gain\":%u,", s->status.awb_gain);
        p+=sprintf(p, "\"aec\":%u,", s->status.aec);
        p+=sprintf(p, "\"aec2\":%u,", s->status.aec2);
        p+=sprintf(p, "\"ae_level\":%d,", s->status.ae_level);
        p+=sprintf(p, "\"aec_value\":%u,", s->status.aec_value);
        p+=sprintf(p, "\"agc\":%u,", s->status.agc);
        p+=sprintf(p, "\"agc_gain\":%u,", s->status.agc_gain);
        p+=sprintf(p, "\"gainceiling\":%u,", s->status.gainceiling);
        p+=sprintf(p, "\"bpc\":%u,", s->status.bpc);
        p+=sprintf(p, "\"wpc\":%u,", s->status.wpc);
        p+=sprintf(p, "\"raw_gma\":%u,", s->status.raw_gma);
        p+=sprintf(p, "\"lenc\":%u,", s->status.lenc);
        p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror);
        p+=sprintf(p, "\"dcw\":%u,", s->status.dcw);
        p+=sprintf(p, "\"colorbar\":%u", s->status.colorbar);
        *p++ = '}';
        *p++ = 0;
        httpd_resp_set_type(req, "application/json");
        httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
        return httpd_resp_send(req, json_response, strlen(json_response));
    }
    
    
    static esp_err_t state_handler(httpd_req_t *req){
        char*  buf;
        size_t buf_len;
        char cmd[32] = {0,};
    
        buf_len = httpd_req_get_url_query_len(req) + 1;
        if (buf_len > 1) {
            buf = (char*)malloc(buf_len);
            Serial.println(buf);
            
            if(!buf){
                httpd_resp_send_500(req);
                return ESP_FAIL;
            }
            
            if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
                if (httpd_query_key_value(buf, "cmd", cmd, sizeof(cmd)) == ESP_OK) {
                  
                } else {
                    free(buf);
                    Serial.print("*");
                    Serial.println(ESP_FAIL);
                    httpd_resp_send_404(req);
                    return ESP_FAIL;
                }
            } else {
                free(buf);
                Serial.print("**");
                Serial.println(ESP_FAIL);
                httpd_resp_send_404(req);
                return ESP_FAIL;
            }
            free(buf);
            
        } else {
            Serial.print("***");
            Serial.println(ESP_FAIL);
            httpd_resp_send_404(req);
            return ESP_FAIL;
        }
    
        int res = 0;
    
        if(!strcmp(cmd, "F")) {
          Serial.println("Forward");
          digitalWrite(DRV_A, HIGH);
          digitalWrite(DRV_B, HIGH);
          digitalWrite(DIR_A, HIGH);
          digitalWrite(DIR_B, HIGH);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "B")) {
          Serial.println("Backward");
          digitalWrite(DRV_A, HIGH);
          digitalWrite(DRV_B, HIGH);
          digitalWrite(DIR_A, LOW);
          digitalWrite(DIR_B, LOW);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "R")) {
          Serial.println("Turn Right");
          digitalWrite(DRV_A, HIGH);
          digitalWrite(DRV_B, HIGH);
          digitalWrite(DIR_A, LOW);
          digitalWrite(DIR_B, HIGH);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "L")) {
          Serial.println("Turn Left");
          digitalWrite(DRV_A, HIGH);
          digitalWrite(DRV_B, HIGH);
          digitalWrite(DIR_A, HIGH);
          digitalWrite(DIR_B, LOW);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "G")) {
          Serial.println("Forward Left");
          digitalWrite(DRV_A, HIGH);
          digitalWrite(DRV_B, LOW);
          digitalWrite(DIR_A, HIGH);
          digitalWrite(DIR_B, HIGH);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "H")) {
          Serial.println("Backward Left");
          digitalWrite(DRV_A, HIGH);
          digitalWrite(DRV_B, LOW);
          digitalWrite(DIR_A, LOW);
          digitalWrite(DIR_B, LOW);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "I")) {
          Serial.println("Forward Right");
          digitalWrite(DRV_A, LOW);
          digitalWrite(DRV_B, HIGH);
          digitalWrite(DIR_A, HIGH);
          digitalWrite(DIR_B, HIGH);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "J")) {
          Serial.println("Backward Right");
          digitalWrite(DRV_A, LOW);
          digitalWrite(DRV_B, HIGH);
          digitalWrite(DIR_A, LOW);
          digitalWrite(DIR_B, LOW);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "S")) {
          Serial.println("Stop");
          digitalWrite(DRV_A, LOW);
          digitalWrite(DRV_B, LOW);
          digitalWrite(DIR_A, LOW);
          digitalWrite(DIR_B, LOW);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "V")) {
          Serial.println("Horn On");
          digitalWrite(buzzerPin, HIGH);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "v")) {
          Serial.println("Horn Off");
          digitalWrite(buzzerPin, LOW);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "W")) {
          Serial.println("LED On");
          ledcWrite(7, ledVal);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if(!strcmp(cmd, "w")) {
          Serial.println("LED Off");
          ledcWrite(7, 0);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else if (!strcmp(cmd, "x")){
          Serial.println("Flash Light : Low (20)");
          ledVal = 20;
          ledcWrite(7, ledVal);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "y")){
          Serial.println("Flash Light : Medium (50)");
          ledVal = 50;
          ledcWrite(7, ledVal);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "z")){
          Serial.println("Flash Light : Bright (100)");
          ledVal = 100;
          ledcWrite(7, ledVal);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }    
        else if (!strcmp(cmd, "Z")){
          Serial.println("Flash Light : Super Bright (255)");
          ledVal = 255;
          ledcWrite(7, ledVal);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }   
        
        /* Controlling the servo motor angle with PWM */
        /* ledcWrite(Channel, Dutycycle) dutycycle range : 3250-6500*/
        else if (!strcmp(cmd, "0")){
          Serial.println("Servo 0 (3250)");
          ledcWrite(8, 3250);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "1")){
          Serial.println("Servo 1 (3575)");
          ledcWrite(8, 3575);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "2")){
          Serial.println("Servo 2 (3900)");
          ledcWrite(8, 3900);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "3")){
          Serial.println("Servo 3 (4225)");
          ledcWrite(8, 4225);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "4")){
          Serial.println("Servo 4 (4550)");
          ledcWrite(8, 4550);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "5")){
          Serial.println("Servo 5 (4875)");
          ledcWrite(8, 4875);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "6")){
          Serial.println("Servo 6 (5200)");
          ledcWrite(8, 5200);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "7")){
          Serial.println("Servo 7 (5525)");
          ledcWrite(8, 5525);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "8")){
          Serial.println("Servo 8 (5850)");
          ledcWrite(8, 5850);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "9")){
          Serial.println("Servo 9 (6175)");
          ledcWrite(8, 6175);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        else if (!strcmp(cmd, "q")){
          Serial.println("Servo q (6500)");
          ledcWrite(8, 6500);
          httpd_resp_set_type(req, "text/html");
          return httpd_resp_send(req, "OK", 2);
        }
        
        else {
            res = -1;
        }
    
        if(res){
            return httpd_resp_send_500(req);
        }
    
        httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
        return httpd_resp_send(req, NULL, 0);
    }
    
    
    void startCameraServer(){
        httpd_config_t config = HTTPD_DEFAULT_CONFIG();
        
    
        httpd_uri_t status_uri = {
            .uri       = "/status",
            .method    = HTTP_GET,
            .handler   = status_handler,
            .user_ctx  = NULL
        };
    
        httpd_uri_t cmd_uri = {
            .uri       = "/control",
            .method    = HTTP_GET,
            .handler   = cmd_handler,
            .user_ctx  = NULL
        };
    
        httpd_uri_t capture_uri = {
            .uri       = "/capture",
            .method    = HTTP_GET,
            .handler   = capture_handler,
            .user_ctx  = NULL
        };
    
       httpd_uri_t stream_uri = {
            .uri       = "/stream",
            .method    = HTTP_GET,
            .handler   = stream_handler,
            .user_ctx  = NULL
        };
    
       httpd_uri_t state_uri = {
            .uri       = "/state",
            .method    = HTTP_GET,
            .handler   = state_handler,
            .user_ctx  = NULL
        };
    
        Serial.printf("Starting web server on port: '%d'", config.server_port);
        if (httpd_start(&camera_httpd, &config) == ESP_OK) {
            httpd_register_uri_handler(camera_httpd, &cmd_uri);
            httpd_register_uri_handler(camera_httpd, &capture_uri);
            httpd_register_uri_handler(camera_httpd, &status_uri);
            httpd_register_uri_handler(camera_httpd, &state_uri);
        }
    
        config.server_port += 1;
        config.ctrl_port += 1;
        Serial.printf("Starting stream server on port: '%d'", config.server_port);
        if (httpd_start(&stream_httpd, &config) == ESP_OK) {
            httpd_register_uri_handler(stream_httpd, &stream_uri);
        }
    }
    

    Mainkan Mobil RC Wifi Camera

    Mobil RC Wifi Camera dapat dikontrol melalui smartphone Android dengan dua cara, dikontrol langsung antara smartphone Android ke ESP32-Cam (mode AP), dengan cara hubungkan smartphone Android ke SSID Wifi dari ESP32-Cam kemudian atur alamat IP pada aplikasi menjadi 192.168.4.1.

    Cara kedua adalah ESP32-Cam (mode STA), dimana smartphone Android terhubung ke jaringan Wifi router yang sama kemudian atur alamat IP pada aplikasi sesuai dengan alamat IP yang dimiliki oleh ESP32-Cam (contoh:192.168.0.2).