LoginSignup
8
9

More than 3 years have passed since last update.

ESP32-WROOM-32で撮影した画像をAWS(API Gateway→Lambda→S3)へ送信

Last updated at Posted at 2019-07-09

概要

題名のままですが、ESP32-WROOM-32で撮影した画像をAWS(API Gateway→Lambda→S3)へ送信してみました!

カメラはOV2640を使用しています。ESP32での制御方法は以前の記事にまとめています。本記事では既に回路図作成済み前提で話を進めます。
https://qiita.com/koki-iwaizumi/items/db81c46b0d1b39b01655

DSC_0418.JPG

S3バケット作成

画像保存先のS3のバケットを作成しておきます。バケット作成するだけなので詳細は割愛します。

lambda設定

下記を参考にlambda関数を作成します。今回はAPIキーあり、Python3.7を使用して関数を作成しました。
https://dev.classmethod.jp/cloud/aws/getting-start-api-gateway/

関数作成後、その関数にS3のロールを割り当てる必要があるので下記を参考にS3を割り当てます。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-s3-example.html

下記がlambda_functionになります。BUCKET_NAMEはS3のバケットネームに変更してください。
処理の中身としては、json形式のデータ{"image":"base64で変換したjpeg"}を受信し、base64デーコード後、S3へランダム名で保存されるようになっています。※base64で変換できない場合などの例外処理などはしてないので、後で追加する予定です、、

トリガーをAPI Gatewayに設定し、APIキーありで保存します。

lambda_function.py

import boto3
import base64
import uuid

BUCKET_NAME = '*'

def lambda_handler(event, context):

    response =  {
        "status": "failed",
    }

    print(event)

    if "image" not in event:
        return response


    s3 = boto3.resource('s3')
    bucket = s3.Bucket(BUCKET_NAME)
    bucket.put_object(
        Key=f'{uuid.uuid4()}.jpg',
        Body=base64.b64decode(event["image"]),
        ContentType='image/jpeg')

    response["status"] = "success"

    return response

ESP-WROOM-32

下記のプログラムをESP-WROOM-32に書き込みます。全ファイルはgitに挙げています。
https://github.com/koki-iwaizumi/esp32-ov2640/tree/aws-lambda

wifi接続後、OV2640で画像撮影、jpegデータをbase64に変換、postメソッドでAPI Gatewayへリクエストしています。jpegデータはVGA(640px:480px)のサイズにしています。

下記の変数はそれぞれ任意の値を入力してください。
WIFI_SSID、WIFI_PASSWORD、AWS_ENDPOINT、AWS_API_KEY

app_main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"

#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_http_client.h"
#include "esp_log.h"
#include "esp_err.h"
#include "nvs_flash.h"

#include "driver/i2c.h"
#include "driver/gpio.h"
#include "camera.h"
#include "http_server.h"

#include "mbedtls/base64.h"

/********* config ***********/
#define WIFI_SSID "*"
#define WIFI_PASSWORD "*"
#define AWS_ENDPOINT "*"
#define AWS_API_KEY "*"

/********* i2c ***********/
#define I2C_CAMERA_TX_BUF_DISABLE 0
#define I2C_CAMERA_RX_BUF_DISABLE 0
#define I2C_CAMERA_FREQ_HZ 100000
#define I2C_CAMERA_NUM I2C_NUM_0
#define ACK_CHECK_EN 0x1
#define ACK_CHECK_DIS 0x0
#define ACK_VAL 0x0
#define NACK_VAL 0x1

#define CAMERA_SLAVE_ADDRESS_READ 0b01100001
#define CAMERA_SLAVE_ADDRESS_WRITE 0b01100000

/********* gpio ***********/
#define PIN_D0 35
#define PIN_D1 17
#define PIN_D2 34
#define PIN_D3 5
#define PIN_D4 39
#define PIN_D5 18
#define PIN_D6 36
#define PIN_D7 19
#define PIN_XCLK 27
#define PIN_PCLK 21
#define PIN_VSYNC 22
#define PIN_HREF 26
#define PIN_SDA 25
#define PIN_SCL 23
#define PIN_RESET 15

#define GPIO_OUTPUT_PIN_SEL  (1 << PIN_RESET)

static const char* TAG = "camera_demo app_main";

static EventGroupHandle_t s_wifi_event_group;
const int CONNECTED_BIT = BIT0;
static ip4_addr_t s_ip_addr;

/********* camera reg ***********/
static const uint8_t default_regs[][2] = {
    {0xFF, 0x00},
    {0x2C, 0xFF},
    {0x2E, 0xDF},

    {0xFF, 0x01},
    {0x3C, 0x32},
    {0x11, 0x80},
    {0x09, 0x02},
    {0x28, 0x00},
    {0x13, 0xE5},
    {0x14, 0x48},
    {0x15, 0x00},
    {0x2C, 0x0C},
    {0x33, 0x78},
    {0x3A, 0x33},
    {0x3B, 0xFB},
    {0x3E, 0x00},
    {0x43, 0x11},
    {0x16, 0x10},
    {0x39, 0x02},
    {0x35, 0x88},
    {0x22, 0x0A},
    {0x37, 0x40},
    {0x23, 0x00},
    {0x34, 0xA0},
    {0x06, 0x02},
    {0x06, 0x88},
    {0x07, 0xC0},
    {0x0D, 0xB7},
    {0x0E, 0x01},
    {0x4C, 0x00},
    {0x4A, 0x81},
    {0x21, 0x99},
    {0x24, 0x40},
    {0x25, 0x38},
    {0x26, 0x82},
    {0x48, 0x00},
    {0x49, 0x00},
    {0x5C, 0x00},
    {0x63, 0x00},
    {0x46, 0x00},
    {0x47, 0x00},
    {0x0C, 0x3A},
    {0x5D, 0x55},
    {0x5E, 0x7D},
    {0x5F, 0x7D},
    {0x60, 0x55},
    {0x61, 0x70},
    {0x62, 0x80},
    {0x7C, 0x05},
    {0x20, 0x80},
    {0x28, 0x30},
    {0x6C, 0x00},
    {0x6D, 0x80},
    {0x6E, 0x00},
    {0x70, 0x02},
    {0x71, 0x94},
    {0x73, 0xC1},
    {0x3D, 0x34},
    {0x5A, 0x57},
    {0x4F, 0xBB},
    {0x50, 0x9C},

    {0xFF, 0x00},
    {0xE5, 0x7F},
    {0xF9, 0xC0},
    {0x41, 0x24},
    {0xE0, 0x14},
    {0x76, 0xFF},
    {0x33, 0xA0},
    {0x42, 0x20},
    {0x43, 0x18},
    {0x4C, 0x00},
    {0x87, 0xD0},
    {0x88, 0x3F},
    {0xD7, 0x03},
    {0xD9, 0x10},
    {0xD3, 0x82},
    {0xC8, 0x08},
    {0xC9, 0x80},
    {0x7C, 0x00},
    {0x7D, 0x00},
    {0x7C, 0x03},
    {0x7D, 0x48},
    {0x7D, 0x48},
    {0x7C, 0x08},
    {0x7D, 0x20},
    {0x7D, 0x10},
    {0x7D, 0x0E},
    {0x90, 0x00},
    {0x91, 0x0E},
    {0x91, 0x1A},
    {0x91, 0x31},
    {0x91, 0x5A},
    {0x91, 0x69},
    {0x91, 0x75},
    {0x91, 0x7E},
    {0x91, 0x88},
    {0x91, 0x8F},
    {0x91, 0x96},
    {0x91, 0xA3},
    {0x91, 0xAF},
    {0x91, 0xC4},
    {0x91, 0xD7},
    {0x91, 0xE8},
    {0x91, 0x20},
    {0x92, 0x00},
    {0x93, 0x06},
    {0x93, 0xE3},
    {0x93, 0x03},
    {0x93, 0x03},
    {0x93, 0x00},
    {0x93, 0x02},
    {0x93, 0x00},
    {0x93, 0x00},
    {0x93, 0x00},
    {0x93, 0x00},
    {0x93, 0x00},
    {0x93, 0x00},
    {0x93, 0x00},
    {0x96, 0x00},
    {0x97, 0x08},
    {0x97, 0x19},
    {0x97, 0x02},
    {0x97, 0x0C},
    {0x97, 0x24},
    {0x97, 0x30},
    {0x97, 0x28},
    {0x97, 0x26},
    {0x97, 0x02},
    {0x97, 0x98},
    {0x97, 0x80},
    {0x97, 0x00},
    {0x97, 0x00},
    {0xA4, 0x00},
    {0xA8, 0x00},
    {0xC5, 0x11},
    {0xC6, 0x51},
    {0xBF, 0x80},
    {0xC7, 0x10},
    {0xB6, 0x66},
    {0xB8, 0xA5},
    {0xB7, 0x64},
    {0xB9, 0x7C},
    {0xB3, 0xAF},
    {0xB4, 0x97},
    {0xB5, 0xFF},
    {0xB0, 0xC5},
    {0xB1, 0x94},
    {0xB2, 0x0F},
    {0xC4, 0x5C},
    {0xA6, 0x00},
    {0xA7, 0x20},
    {0xA7, 0xD8},
    {0xA7, 0x1B},
    {0xA7, 0x31},
    {0xA7, 0x00},
    {0xA7, 0x18},
    {0xA7, 0x20},
    {0xA7, 0xD8},
    {0xA7, 0x19},
    {0xA7, 0x31},
    {0xA7, 0x00},
    {0xA7, 0x18},
    {0xA7, 0x20},
    {0xA7, 0xD8},
    {0xA7, 0x19},
    {0xA7, 0x31},
    {0xA7, 0x00},
    {0xA7, 0x18},
    {0x7F, 0x00},
    {0xE5, 0x1F},
    {0xE1, 0x77},
    {0xDD, 0x7F},
    {0xC2, 0x0E},

    {0x00, 0x00}
};

static const uint8_t svga_regs[][2] = {
    {0xFF, 0x01},
    {0x12, 0x40},
    {0x03, 0x0F},
    {0x32, 0x09},
    {0x17, 0x11},
    {0x18, 0x43},
    {0x19, 0x00},
    {0x1A, 0x4B},
    {0x3D, 0x38},
    {0x35, 0xDA},
    {0x22, 0x1A},
    {0x37, 0xC3},
    {0x34, 0xC0},
    {0x06, 0x88},
    {0x0D, 0x87},
    {0x0E, 0x41},
    {0x42, 0x03},

    {0xFF, 0x00},
    {0x05, 0x01},
    {0xE0, 0x04},
    {0xC0, 0x64},
    {0xC1, 0x4B},
    {0x8C, 0x00},
    {0x53, 0x00},
    {0x54, 0x00},
    {0x51, 0xC8},
    {0x52, 0x96},
    {0x55, 0x00},
    {0x57, 0x00},
    {0x86, 0x3D},
    {0x50, 0x80},
    {0xD3, 0x80},
    {0x05, 0x00},
    {0xE0, 0x00},

    {0x00, 0x00}
};

static const uint8_t jpeg_regs[][2] = {
    {0xFF, 0x00},
    {0xE0, 0x04},
    {0xDA, 0x18},
    {0xD7, 0x03},
    {0xE1, 0x77},
    {0x44, 0x0C},
    {0xE0, 0x00},

    {0x00, 0x00}
};

static const uint8_t framesize_low_regs[][2] = {
    {0xFF, 0x00},
    {0x05, 0x01},
    {0x5A, 0xA0},
    {0x5B, 0x78},
    {0x5C, 0x00},

    {0xFF, 0x01},
    {0x11, 0x83},

    {0x00, 0x00}
};

static const uint8_t framesize_high_regs[][2] = {
    {0xFF, 0x00},
    {0x05, 0x00},

    {0x00, 0x00}
};

static const uint8_t quality_regs[][2] = {
    {0xFF, 0x00},
    {0x44, 0x0F},

    {0x00, 0x00}
};

esp_err_t read_camera_config(uint8_t reg, uint8_t* data){

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, CAMERA_SLAVE_ADDRESS_WRITE, ACK_CHECK_EN);
    i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
    i2c_master_stop(cmd);
    esp_err_t err = i2c_master_cmd_begin(I2C_CAMERA_NUM, cmd, 10 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);

    if(err != ESP_OK){
        return err;
    }

    cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, CAMERA_SLAVE_ADDRESS_READ, ACK_CHECK_EN);
    i2c_master_read_byte(cmd, data, NACK_VAL);
    i2c_master_stop(cmd);
    err = i2c_master_cmd_begin(I2C_CAMERA_NUM, cmd, 10 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);

    if(err != ESP_OK){
        return err;
    }

    return ESP_OK;
}

esp_err_t write_camera_config(uint8_t reg, uint8_t data){

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, CAMERA_SLAVE_ADDRESS_WRITE, ACK_CHECK_EN);
    i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
    i2c_master_write_byte(cmd, data, ACK_CHECK_EN);
    i2c_master_stop(cmd);
    esp_err_t err = i2c_master_cmd_begin(I2C_CAMERA_NUM, cmd, 10 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);

    if(err != ESP_OK){
        return err;
    }

    return ESP_OK;
}

static esp_err_t event_handler(void *ctx, system_event_t *event){

    switch(event->event_id){
        case SYSTEM_EVENT_STA_START:
            esp_wifi_connect();
            break;
        case SYSTEM_EVENT_STA_GOT_IP:
            xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);
            s_ip_addr = event->event_info.got_ip.ip_info.ip;
            break;
        case SYSTEM_EVENT_STA_DISCONNECTED:
            esp_wifi_connect();
            xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);
            break;
        default:
            break;
    }
    return ESP_OK;
}

static void initialise_wifi(void){

    tcpip_adapter_init();
    s_wifi_event_group = xEventGroupCreate();
    esp_event_loop_init(event_handler, NULL);
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);
    esp_wifi_set_storage(WIFI_STORAGE_RAM);
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = WIFI_SSID,
            .password = WIFI_PASSWORD,
        },
    };
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
    esp_wifi_start();
    esp_wifi_set_ps(WIFI_PS_NONE);
    ESP_LOGI(TAG, "Connecting to \"%s\"", wifi_config.sta.ssid);
    xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);
    ESP_LOGI(TAG, "Connected");
}

esp_err_t init_camera_clock(camera_config_t* config){

    periph_module_enable(PERIPH_LEDC_MODULE);

    ledc_timer_config_t timer_conf = {
        .duty_resolution = 1,
        .freq_hz = config->xclk_freq_hz,
        .speed_mode = LEDC_HIGH_SPEED_MODE,
        .timer_num = config->ledc_timer
    };
    esp_err_t err = ledc_timer_config(&timer_conf);
    if(err != ESP_OK){
        ESP_LOGE(TAG, "ledc_timer_config failed, err=%d", err);
        return err;
    }

    ledc_channel_config_t ch_conf = {
        .channel = config->ledc_channel,
        .timer_sel = config->ledc_timer,
        .intr_type = LEDC_INTR_DISABLE,
        .duty = 1,
        .speed_mode = LEDC_HIGH_SPEED_MODE,
        .gpio_num = config->pin_xclk
    };
    err = ledc_channel_config(&ch_conf);
    if(err != ESP_OK){
        ESP_LOGE(TAG, "ledc_channel_config failed, err=%d", err);
        return err;
    }

    return ESP_OK;
}

void init_i2c(camera_config_t* config){
    int i2c_master_port = I2C_NUM_0;
    i2c_config_t conf;
    conf.mode = I2C_MODE_MASTER;
    conf.sda_io_num = config->pin_sscb_sda;
    conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
    conf.scl_io_num = config->pin_sscb_scl;
    conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
    conf.master.clk_speed = I2C_CAMERA_FREQ_HZ;
    i2c_param_config(i2c_master_port, &conf);
    i2c_driver_install(i2c_master_port, conf.mode, I2C_CAMERA_RX_BUF_DISABLE, I2C_CAMERA_TX_BUF_DISABLE, 0);
}

esp_err_t _http_event_handle(esp_http_client_event_t *evt){
    switch(evt->event_id){
        case HTTP_EVENT_ERROR:
            ESP_LOGI(TAG, "HTTP_EVENT_ERROR");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
            break;
        case HTTP_EVENT_HEADER_SENT:
            ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
            break;
        case HTTP_EVENT_ON_HEADER:
            ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER");
            printf("%.*s", evt->data_len, (char*)evt->data);
            break;
        case HTTP_EVENT_ON_DATA:
            ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
            if( ! esp_http_client_is_chunked_response(evt->client)){
                printf("%.*s", evt->data_len, (char*)evt->data);
            }
            break;
        case HTTP_EVENT_ON_FINISH:
            ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH");
            break;
        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
            break;
    }
    return ESP_OK;
}

static void http_post_task(){

    esp_http_client_config_t http_config = {
        .url = AWS_ENDPOINT,
        .event_handler = _http_event_handle,
        .method = HTTP_METHOD_POST,
    };

    unsigned char image0[15000];
    char image[15000];
    size_t olen;

    mbedtls_base64_encode(image0, sizeof(image0), &olen, camera_get_fb(), camera_get_data_size());
    sprintf(image, "{\"image\":\"%s\"}", image0);
    const char *post_data = (const char*)image;

    esp_http_client_handle_t client = esp_http_client_init(&http_config);
    esp_http_client_set_post_field(client, post_data, strlen(post_data));
    esp_http_client_set_header(client, "x-api-key", AWS_API_KEY);
    esp_http_client_set_header(client, "Content-Type", "application/json");

    esp_err_t err = esp_http_client_perform(client);
    if(err == ESP_OK){
       ESP_LOGI(TAG, "Status = %d, content_length = %d", esp_http_client_get_status_code(client), esp_http_client_get_content_length(client));
    }
    esp_http_client_cleanup(client);

    while(1);
}

void app_main(){

    esp_log_level_set("*", ESP_LOG_VERBOSE);

    esp_err_t err = nvs_flash_init();
    if(err != ESP_OK){
        nvs_flash_erase();
        nvs_flash_init();
    }

    gpio_install_isr_service(0);

    camera_config_t camera_config = {
        .ledc_channel = LEDC_CHANNEL_0,
        .ledc_timer = LEDC_TIMER_0,
        .pin_d0 = PIN_D0,
        .pin_d1 = PIN_D1,
        .pin_d2 = PIN_D2,
        .pin_d3 = PIN_D3,
        .pin_d4 = PIN_D4,
        .pin_d5 = PIN_D5,
        .pin_d6 = PIN_D6,
        .pin_d7 = PIN_D7,
        .pin_xclk = PIN_XCLK,
        .pin_pclk = PIN_PCLK,
        .pin_vsync = PIN_VSYNC,
        .pin_href = PIN_HREF,
        .pin_sscb_sda = PIN_SDA,
        .pin_sscb_scl = PIN_SCL,
        .pin_reset = PIN_RESET,

        .xclk_freq_hz = 6000000,
        .width = 640,
        .height = 480,
    };

    ESP_LOGE(TAG, "xclk_freq_hz=%d[Hz]", camera_config.xclk_freq_hz);

    //GPIO設定
    gpio_config_t io_conf;
    io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    io_conf.pull_down_en = 0;
    io_conf.pull_up_en = 0;
    gpio_config(&io_conf);

    //CAMERA クロック出力
    init_camera_clock(&camera_config);

    //I2C 初期化
    init_i2c(&camera_config);

    //CAMERA リセット
    gpio_set_level(PIN_RESET, 0);
    vTaskDelay(10 / portTICK_RATE_MS);
    gpio_set_level(PIN_RESET, 1);
    vTaskDelay(10 / portTICK_RATE_MS);

    //CAMERA OV2640確認
    uint8_t camera_pid_h;
    read_camera_config(0x0A, &camera_pid_h);
    ESP_LOGI(TAG, "camera_pid_h=0x%02X", camera_pid_h);
    if(camera_pid_h != 0x26){
        ESP_LOGE(TAG, "failed, camera_pid_h=%02X", camera_pid_h);
        return;
    }

    //CAMERA システムリセット
    write_camera_config(0xFF, 0x01);
    write_camera_config(0x12, 0x80);
    vTaskDelay(10 / portTICK_RATE_MS);

    //CAMERA 設定書き込み
    int i = 0;
    while(default_regs[i][0]){
        write_camera_config(default_regs[i][0], default_regs[i][1]);
        i++;
    }
    i = 0;
    while(jpeg_regs[i][0]){
        write_camera_config(jpeg_regs[i][0], jpeg_regs[i][1]);
        i++;
    }
    i = 0;
    while(framesize_low_regs[i][0]){
        write_camera_config(framesize_low_regs[i][0], framesize_low_regs[i][1]);
        i++;
    }
    i = 0;
    while(svga_regs[i][0]){
        write_camera_config(svga_regs[i][0], svga_regs[i][1]);
        i++;
    }
    i = 0;
    while(framesize_high_regs[i][0]){
        write_camera_config(framesize_high_regs[i][0], framesize_high_regs[i][1]);
        i++;
    }
    i = 0;
    while(quality_regs[i][0]){
        write_camera_config(quality_regs[i][0], quality_regs[i][1]);
        i++;
    }

    //CAMERA その他設定
    err = camera_init(&camera_config);
    if(err != ESP_OK){
        ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
        return;
    }

    //CAMERA wifi設定
    initialise_wifi();

    //写真撮影
    err = camera_run();
    if(err != ESP_OK){
        ESP_LOGD(TAG, "Camera capture failed with error = %d", err);
        return;
    }

    xTaskCreate(&http_post_task, "http_post_task", 50000, NULL, 5, NULL);

    vTaskDelay(10000 / portTICK_RATE_MS);
}

実行

ESP32を実行すると、S3に撮影した画像が保存されます。

参考まとめ

ゼロから作りながら覚えるAPI Gateway環境構築
https://dev.classmethod.jp/cloud/aws/getting-start-api-gateway/

lambda (トリガー:APIGateway)
https://blog.apar.jp/web/10804/

S3のロールを作成
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-s3-example.html

APIGateway + Lambdaでbase64変換された画像ファイルをS3に保存する
https://qiita.com/yumakkt/items/1a62d279e81d9f6d0c63

[Python]Base64でエンコードされた画像データをデコードする。
https://qiita.com/TsubasaSato/items/908d4f5c241091ecbf9b

M5Cameraから画像をPOSTする
https://qiita.com/dinosauria123/items/149535349d98f51bd876

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9