M5Cameraをアクセスポイントにつなげて映像を見る

M5Cameraに直接接続して映像を見る
https://qiita.com/nakazawaken1/items/ed4a812a07cc4a45f907
では、カメラにスマホ等を直接接続して映像を見るため、同時にインターネットに接続することができませんでした。
今回は、カメラをルータ等のアクセスポイントにつなげてアクセスポイント経由でカメラの映像を見れるようにします。
また、わかりやすいように、アクセスポイントにつながるまではLEDが点灯し、つながれば消灯するようにしています。
以下の２行の内容を修正してからプログラムを書き込んでください。
`#define SSID "接続可能なSSIDを指定してください"`
`#define PASSWORD "パスワードを指定してください"`


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

// アクセスポイントのSSID(2.4GHzのみ対応)
#define SSID "接続可能なSSIDを指定してください"

// アクセスポイントのパスワード
#define PASSWORD "パスワードを指定してください"

// 1: 上下反転する, 0: しない
#define FLIP 1

// 区切り用のランダム文字列（変更不要）
#define BOUNDARY_KEY  "123456789000000000000987654321"

void setup() {

    // シリアル通信開始
    Serial.begin(115200);
    Serial.setDebugOutput(true);
    Serial.println("power on");

    // LED初期化
    Serial.println("led begin");
    pinMode(GPIO_NUM_14, OUTPUT); // GPIO14番を出力モードに設定
    digitalWrite(GPIO_NUM_14, LOW); // LED点灯

    // WiFi アクセスポイントモード設定
    Serial.println("WiFi station mode");
    WiFi.mode(WIFI_MODE_STA);
    WiFi.begin(SSID, PASSWORD);
    for (int i = 0; WiFi.status() != WL_CONNECTED; i++) { // 接続できるまで
        if(i >= 60) esp_restart(); // 60秒つながらない場合はリブート
        delay(1000);
        Serial.print(".");
    }
    digitalWrite(GPIO_NUM_14, HIGH); // LED消灯
    Serial.printf("Connected " SSID "\nPlease browse http://%s\n", WiFi.localIP().toString().c_str());

    // カメラ接続
    Serial.println("camera begin");
    camera_config_t config = {
        .pin_pwdn = -1,
        .pin_reset = 15,
        .pin_xclk = 27,
        .pin_sscb_sda = 22,
        .pin_sscb_scl = 23,
        .pin_d7 = 19,
        .pin_d6 = 36,
        .pin_d5 = 18,
        .pin_d4 = 39,
        .pin_d3 = 5,
        .pin_d2 = 34,
        .pin_d1 = 35,
        .pin_d0 = 32,
        .pin_vsync = 25,
        .pin_href = 26,
        .pin_pclk = 21,
        .xclk_freq_hz = 20000000, // 20MHz
        .ledc_timer = LEDC_TIMER_0, // 0番のタイマー使用
        .ledc_channel = LEDC_CHANNEL_0, // 0番のチャネル使用
        .pixel_format = PIXFORMAT_JPEG, // JPEG
        .frame_size = FRAMESIZE_VGA, // 解像度
        .jpeg_quality = 12, // JPGE画質（小さいほど高画質）
        .fb_count = 1 // フレームバッファ数（２つあれば２倍で処理できる？）
    };  
    esp_err_t result = esp_camera_init(&config);
    if (result != ESP_OK) {
        Serial.printf("esp_camera_init error 0x%x\n", result);
        return;
    }

    // 上下反転設定
    sensor_t * sensor = esp_camera_sensor_get();
    sensor->set_vflip(sensor, FLIP);

    // HTTP Server 開始
    Serial.println("httpd begin");
    httpd_handle_t httpd = NULL;
    httpd_config_t httpd_config = HTTPD_DEFAULT_CONFIG();
    Serial.printf("lisen port %d\n", httpd_config.server_port);
    result = httpd_start(&httpd, &httpd_config);
    if (result != ESP_OK) {
        Serial.printf("httpd_start error 0x%x\n", result);
        return;
    }

    // 映像送信処理登録
    httpd_uri_t stream = {
        .uri       = "/",
        .method    = HTTP_GET,
        .handler   = [](httpd_req_t *request) {
            Serial.println("GET /");

            // レスポンスの種類を指定
            esp_err_t result = httpd_resp_set_type(request, "multipart/x-mixed-replace;boundary=" BOUNDARY_KEY);
            if (result != ESP_OK) {
                Serial.printf("httpd_resp_set_type(multipart) error 0x%x\n", result);
            }

            // 繰り返し画像を送信（動画のように見える）
            while (result == ESP_OK) {

                //撮影
                struct Shot {

                    // カメラフレーム
                    camera_fb_t* frame;

                    // カメラ画像取得
                    Shot() : frame(esp_camera_fb_get()) {
                        if (!ok()) {
                        Serial.println("esp_camera_fb_get failed");
                        }
                    }

                    // 取得成功かどうか
                    inline bool ok() {
                        return frame != NULL;
                    }
                    // バイナリデータ
                    inline uint8_t* data() {
                        return frame->buf;
                    }
                    
                    // バイト数
                    inline size_t size() {
                        return frame->len;
                    }

                    // リソース開放
                    ~Shot() {
                        if (frame != NULL) {
                        esp_camera_fb_return(frame);
                        }
                    }
                } shot;
                if (!shot.ok()) break;

                // ヘッダ部分の送信
                char buffer[64];
                size_t length = snprintf(buffer, sizeof(buffer), "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n", shot.size());
                result = httpd_resp_send_chunk(request, buffer, length);
                if (result != ESP_OK) {
                Serial.printf("httpd_resp_send_chunk(header) error 0x%x\n", result);
                    continue;
                }

                // JPEGの送信
                result = httpd_resp_send_chunk(request, (char*)shot.data(), shot.size());
                if (result != ESP_OK) {
                Serial.printf("httpd_resp_send_chunk(image) error 0x%x\n", result);
                    continue;
                }

                // 区切り送信
                result = httpd_resp_send_chunk(request, "\r\n--" BOUNDARY_KEY "\r\n", -1);
                if (result != ESP_OK) {
                Serial.printf("httpd_resp_send_chunk(boundary) error 0x%x\n", result);
                    continue;
                }
            }
            if (result != ESP_OK) {
                Serial.printf("stream_handler error 0x%x\n", result);
                return httpd_resp_send_500(request);
            }
            return httpd_resp_send(request, NULL, 0);
        },
        .user_ctx  = NULL
    };
    result = httpd_register_uri_handler(httpd, &stream);
    if (result != ESP_OK) {
        Serial.printf("httpd_register_uri_handler(stream) error 0x%x\n", result);
        return;
    }
}

void loop() {
}
```