0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Timer Camera X(M5)で撮影した画像をスマホから見れるようにするまで(Miniviz)

Last updated at Posted at 2025-12-09

はじめに

M5Stack Advent Calendar 2025に参加します。

ここで行うこと

TimerCameraXの環境構築と動作確認をしてみます。
また、Minivizに送信して、スマホから画像を見れるようにします。

Minivizとは

Minivizは、IoTデータを手軽に可視化できるサービスです。
IoTデータを送信するだけで、スマホから簡単に確認できます。

用意するもの

  • ESP32 PSRAM Timer Camera X
  • PlatformIO

minivizを使う場合は

  • MinivizのプロジェクトIDとトークン

環境構築・接続確認

1. PlatformIOでプロジェクト作成

以下のようにプロジェクトを作成します。


image.png

2. ライブラリのサンプルコードを動かしてみる

以下の公式のサンプルコードをダウンロードしてsrc/にソースコードをコピーしましょう。
https://github.com/m5stack/TimerCam-arduino.git

platformio.iniでupload_speedを115200にするとアップロードが安定します。

[env:m5stack-timer-cam]
platform = espressif32
board = m5stack-timer-cam
framework = arduino
lib_deps =https://github.com/m5stack/TimerCam-arduino.git
upload_speed = 115200
monitor_speed = 115200

ローカルで確認してみる

アップロードして、ローカルIPにアクセスしてみましょう。
image.png

image.png

Minivizを使ってスマホでも確認する

プロジェクトIDとトークンを取得

プロジェクトIDとトークンを取得します。

プロジェクト作成 -> プロジェクトIDとトークンを取得

image.png

Minivizに送信するサンプルコード

#include "battery.h"
#include "esp_camera.h"
#include <WiFi.h>
#include <HTTPClient.h>
#include <base64.h>
#include <time.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "camera_pins.h"

// WiFi設定
const char *ssid     = "SSID";
const char *password = "PASSWORD";

// Miniviz設定
const char* PROJECT_ID = "PROJECT_ID";
const char* TOKEN = "TOKEN";
const char* API_URL = "https://api.miniviz.net";
const char* LABEL_KEY = "m5stack_cam";

// 送信間隔(ミリ秒)
const unsigned long SEND_INTERVAL = 60000;  // 60秒

unsigned long lastSendTime = 0;

//----------------------------------------
// NTP Time Synchronization
//----------------------------------------
void syncTime()
{
    Serial.println("Syncing NTP...");
    configTime(0, 0, "ntp.nict.jp", "time.google.com");
    
    struct tm timeinfo;
    while (!getLocalTime(&timeinfo)) {
        delay(200);
        Serial.print("*");
    }
    Serial.println("\nTime synced");
}

//----------------------------------------
// UNIX Timestamp in Milliseconds
//----------------------------------------
uint64_t getTimestampMs()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (uint64_t)tv.tv_sec * 1000ULL + (tv.tv_usec / 1000ULL);
}

void setup() {
    Serial.begin(115200);
    WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);  // disable brownout detector
    
    bat_init();
    bat_hold_output();
    
    Serial.setDebugOutput(true);
    Serial.println();
    
    pinMode(2, OUTPUT);
    digitalWrite(2, HIGH);
    
    // カメラ設定
    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_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;
    config.frame_size   = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count     = 2;
    
    // カメラ初期化
    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();
    s->set_vflip(s, 1);        // flip it back
    s->set_brightness(s, 1);   // up the brightness just a bit
    s->set_saturation(s, -2);  // lower the saturation
    s->set_framesize(s, FRAMESIZE_QVGA);
    
    // WiFi接続
    Serial.printf("Connect to %s, %s\r\n", ssid, password);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    
    // NTP同期
    syncTime();
    
    lastSendTime = millis();
}

String encodeBase64(const uint8_t* data, size_t length) {
    return base64::encode(data, length);
}

bool sendImageToMiniviz(camera_fb_t* fb) {
    if (fb == NULL) {
        Serial.println("[Error] Camera capture failed");
        return false;
    }
    
    Serial.println("[Info] Encoding image to base64...");
    String imageBase64 = encodeBase64(fb->buf, fb->len);
    
    Serial.println("[Info] Sending image to Miniviz...");
    
    HTTPClient http;
    String url = String(API_URL) + "/api/project/" + String(PROJECT_ID) + "/image?token=" + String(TOKEN);
    
    http.begin(url);
    http.addHeader("Content-Type", "application/json");
    
    // タイムスタンプを生成(ミリ秒)
    uint64_t timestamp = getTimestampMs();
    
    // JSONペイロードを作成
    String payload = "{";
    payload += "\"timestamp\":" + String(timestamp) + ",";
    payload += "\"label_key\":\"" + String(LABEL_KEY) + "\",";
    payload += "\"image_name\":\"image.jpg\",";
    payload += "\"image_base64\":\"" + imageBase64 + "\"";
    payload += "}";
    
    int httpResponseCode = http.POST(payload);
    
    if (httpResponseCode > 0) {
        Serial.printf("[Info] HTTP Response code: %d\n", httpResponseCode);
        String response = http.getString();
        Serial.println("[Info] Response: " + response);
        http.end();
        return true;
    } else {
        Serial.printf("[Error] HTTP Error: %s\n", http.errorToString(httpResponseCode).c_str());
        http.end();
        return false;
    }
}

void loop() {
    unsigned long currentTime = millis();
    
    // LED点滅(動作確認用)
    digitalWrite(2, HIGH);
    delay(100);
    digitalWrite(2, LOW);
    delay(100);
    
    // 60秒ごとに画像を送信
    if (currentTime - lastSendTime >= SEND_INTERVAL) {
        Serial.println("[Info] Capturing image...");
        camera_fb_t* fb = esp_camera_fb_get();
        
        if (fb != NULL) {
            bool success = sendImageToMiniviz(fb);
            if (success) {
                Serial.println("[Info] Image sent successfully");
            } else {
                Serial.println("[Error] Failed to send image");
            }
            esp_camera_fb_return(fb);
        } else {
            Serial.println("[Error] Camera capture failed");
        }
        
        lastSendTime = currentTime;
    }
}

アップロードデータの確認

minivizのデータベースメニューからデータを確認します。
image.png

プレビュー

minivizのプレビューメニューからデータを確認します。

image.png

スマホだとこんな感じ

image.png

まとめ

今回はとりあえず環境構築して、動作確認するところまでやってみました。
できれば特徴的な機能である定期実行をトライしたかったのですが、それはまた次回にします。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?