Skip to content

Instantly share code, notes, and snippets.

@Yuikawa-Akira
Last active October 25, 2025 19:50
Show Gist options
  • Select an option

  • Save Yuikawa-Akira/11b63d5a68d71ceaee3f24d3cfb35e0c to your computer and use it in GitHub Desktop.

Select an option

Save Yuikawa-Akira/11b63d5a68d71ceaee3f24d3cfb35e0c to your computer and use it in GitHub Desktop.
Pict_Camera_Stream
#include <WiFi.h>
#include <utility>
#include <esp_http_server.h>
#include <esp_camera.h>
#include <FastLED.h>
#include <SPI.h>
#include <SD.h>
#include <M5Unified.h>
#define KEY_PIN 1
#define LED_PIN 2
#define POWER_GPIO_NUM 18
CRGB LED[1];
camera_fb_t *fb;
M5Canvas canvas_565;
M5Canvas canvas_888;
M5Canvas canvas_dihter;
const char *ssid = "PictCam";
const char *password = "password";
const uint16_t disp_width_pix = 96, disp_height_pix = 96;
// SDカード保存用
char filename[64];
int filecounter = 1;
uint8_t graydata[disp_width_pix * disp_height_pix];
// 最大8色のカラーパレット
uint32_t ColorPalettes[8][8] = {
{ // パレット0 slso8
0x0D2B45, 0x203C56, 0x544E68, 0x8D697A, 0xD08159, 0xFFAA5E, 0xFFD4A3, 0xFFECD6 },
{ // パレット1 都市伝説解体センター風
0x000000, 0x000B22, 0x112B43, 0x437290, 0x437290, 0xE0D8D1, 0xE0D8D1, 0xFFFFFF },
{ // パレット2 ファミレスを享受せよ風
0x010101, 0x33669F, 0x33669F, 0x33669F, 0x498DB7, 0x498DB7, 0xFBE379, 0xFBE379 },
{ // パレット3 gothic-bit
0x0E0E12, 0x1A1A24, 0x333346, 0x535373, 0x8080A4, 0xA6A6BF, 0xC1C1D2, 0xE6E6EC },
{ // パレット4 noire-truth
0x1E1C32, 0x1E1C32, 0x1E1C32, 0x1E1C32, 0xC6BAAC, 0xC6BAAC, 0xC6BAAC, 0xC6BAAC },
{ // パレット5 2BIT DEMIBOY
0x252525, 0x252525, 0x4B564D, 0x4B564D, 0x9AA57C, 0x9AA57C, 0xE0E9C4, 0xE0E9C4 },
{ // パレット6 deep-maze
0x001D2A, 0x085562, 0x009A98, 0x00BE91, 0x38D88E, 0x9AF089, 0xF2FF66, 0xF2FF66 },
{ // パレット7 night-rain
0x000000, 0x012036, 0x3A7BAA, 0x7D8FAE, 0xA1B4C1, 0xF0B9B9, 0xFFD159, 0xFFFFFF },
};
int currentPalettelndex = 0; // 現在のパレットのインデックス
int maxPalettelndex = 7; // パレット総数
int ditherLevels = 0; // ディザ階調数 2以上
camera_config_t camera_config = {
.pin_pwdn = -1,
.pin_reset = -1,
.pin_xclk = 21,
.pin_sscb_sda = 12,
.pin_sscb_scl = 9,
.pin_d7 = 13,
.pin_d6 = 11,
.pin_d5 = 17,
.pin_d4 = 4,
.pin_d3 = 48,
.pin_d2 = 46,
.pin_d1 = 42,
.pin_d0 = 3,
.pin_vsync = 10,
.pin_href = 14,
.pin_pclk = 40,
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_RGB565,
.frame_size = FRAMESIZE_96X96,
// FRAMESIZE_96X96, // 96x96
// FRAMESIZE_128X128, // 128x128
// FRAMESIZE_QQVGA, // 160x120
// FRAMESIZE_QCIF, // 176x144
// FRAMESIZE_HQVGA, // 240x176
// FRAMESIZE_240X240, // 240x240
// FRAMESIZE_QVGA, // 320x240
.jpeg_quality = 0,
.fb_count = 2,
.fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_LATEST,
.sccb_i2c_port = 0,
};
bool loadPaletteFromSD(int paletteIndex) {
if (paletteIndex < 0 || paletteIndex > maxPalettelndex) {
return false;
}
// ファイル名を生成 (例: /ColorPalette0.txt)
String filename = "/ColorPalette" + String(paletteIndex) + ".txt";
// ファイルが存在するか確認
if (!SD.exists(filename)) {
return false; // ファイルが存在しない場合はデフォルトを使うのでfalseを返す
}
// ファイルを開く
File file = SD.open(filename, FILE_READ);
if (!file) {
return false; // ファイルオープン失敗
}
// ファイルから8つのカラーコードを読み込む
int colorCount = 0;
while (file.available() && colorCount < 8) {
String line = file.readStringUntil('\n'); // 1行読み込む
line.trim(); // 前後の空白や改行文字を削除
if (line.length() > 0) {
uint32_t colorValue = strtoul(line.c_str(), NULL, 0);
ColorPalettes[paletteIndex][colorCount] = colorValue;
colorCount++;
}
}
file.close(); // ファイルを閉じる
// 8色読み込めたか確認
if (colorCount == 8) {
return true; // 成功
} else {
// もし8色以下の場合は読み込めた分だけ反映して残りはデフォルトを使用する
return false; // 読み込み失敗(色が足りない)
}
}
bool CameraBegin() {
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
return false;
}
//カメラ追加設定
sensor_t *s = esp_camera_sensor_get();
s->set_vflip(s, 0); //上下反転 0無効 1有効
s->set_hmirror(s, 0); //左右反転 0無効 1有効
// s->set_colorbar(s, 1); //カラーバー 0無効 1有効
// s->set_brightness(s, 1); // up the brightness just a bit
// s->set_saturation(s, 0); // lower the saturation
return true;
}
bool CameraGet() {
fb = esp_camera_fb_get();
if (!fb) {
return false;
}
return true;
}
bool CameraFree() {
if (fb) {
esp_camera_fb_return(fb);
return true;
}
return false;
}
uint16_t swap16(uint16_t value) {
return (value << 8) | (value >> 8);
}
void convertColor_canvas(M5Canvas &srcSprite, M5Canvas &dstSprite) {
int width = std::min(srcSprite.width(), dstSprite.width());
int height = std::min(srcSprite.height(), dstSprite.height());
uint16_t *src_buf = (uint16_t *)srcSprite.getBuffer();
uint8_t *dst_buf = (uint8_t *)dstSprite.getBuffer();
int buf_size = width * height;
for (int i = 0; i < buf_size; ++i) {
uint32_t rgb565Color = swap16(src_buf[i]); // 色を取得
uint32_t rgb888Color = srcSprite.color16to24(rgb565Color); // RGB565からRGB888へ変換
uint8_t r = (rgb888Color >> 16) & 0xFF;
uint8_t g = (rgb888Color >> 8) & 0xFF;
uint8_t b = rgb888Color & 0xFF;
uint16_t luminance = (uint16_t)(0.2126 * r + 0.7152 * g + 0.0722 * b); // 輝度の計算 BT.709の係数を使用
uint8_t grayLevel = luminance / 32; // 輝度を8階調のグレースケールに変換
uint32_t newColor = ColorPalettes[currentPalettelndex][grayLevel]; // カラーパレットから色を取得
uint8_t r_n = (newColor >> 16) & 0xFF;
uint8_t g_n = (newColor >> 8) & 0xFF;
uint8_t b_n = newColor & 0xFF;
dst_buf[3 * i] = r_n; // 取得した色を書込
dst_buf[3 * i + 1] = g_n;
dst_buf[3 * i + 2] = b_n;
}
}
void BayerDither4x4(M5Canvas &srcSprite, M5Canvas &dstSprite, int ditherLevelsPerChannel) {
static const uint8_t bayer4x4[4][4] = {
{ 0, 8, 2, 10 }, { 12, 4, 14, 6 }, { 3, 11, 1, 9 }, { 15, 7, 13, 5 }
};
static const float bayerDivisor = 16.0f;
int width = std::min(srcSprite.width(), dstSprite.width());
int height = std::min(srcSprite.height(), dstSprite.height());
uint16_t *src_buf = (uint16_t *)srcSprite.getBuffer();
uint16_t *dst_buf = (uint16_t *)dstSprite.getBuffer();
float step = 255.0f / (float)(ditherLevelsPerChannel - 1);
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
uint16_t src_color = swap16(src_buf[y * width + x]); // 元画像のピクセル色を取得
uint32_t originalColorValue = srcSprite.color16to24(src_color);
uint8_t r_src = (originalColorValue >> 16) & 0xFF;
uint8_t g_src = (originalColorValue >> 8) & 0xFF;
uint8_t b_src = originalColorValue & 0xFF;
uint8_t r_dst, g_dst, b_dst;
uint8_t channels_src[3] = { r_src, g_src, b_src };
uint8_t channels_dst[3];
float bayerThreshold = (float)bayer4x4[y % 4][x % 4] / bayerDivisor; // 各チャンネルに対してディザリングを適用
for (int ch = 0; ch < 3; ++ch) {
uint8_t val_src = channels_src[ch];
int level_index = floor((float)val_src / step);
if (level_index >= ditherLevelsPerChannel - 1) { level_index = ditherLevelsPerChannel - 2; }
float level_low = (float)level_index * step;
float error = (float)val_src - level_low;
float normalized_error = (step > 0) ? (error / step) : 0.0f;
if (normalized_error < 0.0f) normalized_error = 0.0f;
if (normalized_error > 1.0f) normalized_error = 1.0f;
uint8_t val_dst;
if (normalized_error >= bayerThreshold) {
val_dst = (uint8_t)round(((float)level_index + 1.0f) * step);
} else {
val_dst = (uint8_t)round(level_low);
}
channels_dst[ch] = std::max(0, std::min(255, (int)val_dst));
}
r_dst = channels_dst[0];
g_dst = channels_dst[1];
b_dst = channels_dst[2];
uint16_t ditheredColor = dstSprite.swap565(channels_dst[0], channels_dst[1], channels_dst[2]); // 新しいRGB値で出力先スプライトに描画
dst_buf[y * width + x] = ditheredColor;
}
}
}
//----------------------------------
boolean canStartStream = false;
boolean canSendImage = false;
boolean shouldClear = true;
uint32_t frame_last_time = 0; //for display FPS
uint8_t slider_value1 = 0; // スライダーの値を保持 (0-7)
uint8_t slider_value2 = 0;
//
void *png_data_ptr = NULL;
size_t png_data_len = 0;
//----------------------------------
httpd_handle_t stream_httpd = NULL;
httpd_handle_t control_httpd = NULL;
void setup() {
M5.begin();
Serial.begin(115200);
Serial.println();
delay(1000);
pinMode(POWER_GPIO_NUM, OUTPUT);
digitalWrite(POWER_GPIO_NUM, LOW);
delay(500);
pinMode(KEY_PIN, INPUT_PULLUP);
FastLED.addLeds<SK6812, LED_PIN, GRB>(LED, 1);
LED[0] = CRGB::Red;
FastLED.setBrightness(200);
SPI.begin(7, 8, 6, -1); // 一度SDカードをマウントして確認
if (!SD.begin(15, SPI, 10000000)) {
FastLED.show(); // エラー
delay(500);
return;
} else {
// パレット0から7までループ
for (int i = 0; i <= maxPalettelndex; i++) {
if (loadPaletteFromSD(i)) {
Serial.printf("Palette %d loaded from SD.\n", i);
} else {
Serial.printf("Palette %d use default.\n", i);
}
delay(100);
}
}
SD.end(); // 一旦ENDしておく
if (psramFound()) {
camera_config.pixel_format = PIXFORMAT_RGB565;
camera_config.fb_location = CAMERA_FB_IN_PSRAM;
camera_config.fb_count = 2;
} else {
FastLED.show(); // エラー
delay(500);
}
if (!CameraBegin()) {
FastLED.show(); // エラー
delay(1000);
ESP.restart();
}
delay(500);
LED[0] = CRGB::LimeGreen;
FastLED.setBrightness(200);
FastLED.show();
canvas_565.setColorDepth(16);
canvas_565.createSprite(disp_width_pix, disp_height_pix);
canvas_dihter.setColorDepth(16);
canvas_dihter.createSprite(disp_width_pix, disp_height_pix);
canvas_888.setColorDepth(24);
canvas_888.createSprite(disp_width_pix, disp_height_pix);
TaskHandle_t taskHTTP_handl;
if (!xTaskCreatePinnedToCore(&taskHTTP, "taskHTTP", 9216, NULL, 24, &taskHTTP_handl, 1)) {
Serial.println("Failed to create taskHTTP");
}
while (!canStartStream) {
delay(1);
}
}
void loop() {
if (shouldClear) {
clearAll();
shouldClear = false;
}
if (canStartStream) {
if (!canSendImage) {
CameraGet();
canvas_565.pushImage(0, 0, disp_width_pix, disp_height_pix, (uint16_t *)fb->buf);
CameraFree();
if (ditherLevels == 0) {
convertColor_canvas(canvas_565, canvas_888);
} else {
BayerDither4x4(canvas_565, canvas_dihter, ditherLevels + 1);
convertColor_canvas(canvas_dihter, canvas_888);
}
if (png_data_ptr != NULL) {
free(png_data_ptr);
png_data_ptr = NULL;
png_data_len = 0;
}
png_data_ptr = canvas_888.createPng(&png_data_len, 0, 0, disp_width_pix, disp_height_pix);
if (png_data_ptr != NULL) {
canSendImage = true; // 送信準備完了
} else {
Serial.println("PNG creation failed.");
}
}
}
}
void taskHTTP(void *pvParameters) {
connectToWiFi(ssid, password);
startHttpd();
while (true) {
delay(1);
}
}
void startHttpd() {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = index_handler,
.user_ctx = NULL
};
httpd_uri_t cmd_uri = {
.uri = "/command",
.method = HTTP_GET,
.handler = cmd_handler,
.user_ctx = NULL
};
httpd_uri_t stream_uri = {
.uri = "/stream",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
// 1. コントロールサーバーをポート80 (デフォルト) で開始
if (httpd_start(&control_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(control_httpd, &index_uri);
httpd_register_uri_handler(control_httpd, &cmd_uri);
}
// 2. ストリームサーバーを別のポート (81) で開始
config.server_port += 1; // ポートを 80 から 81 に変更
config.ctrl_port += 1;
stream_httpd = NULL; // 明示的に初期化
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(stream_httpd, &stream_uri);
}
}
static esp_err_t stream_handler(httpd_req_t *req) {
canStartStream = true;
esp_err_t res = ESP_OK;
char *part_buf[64];
// MIMEタイプを image/png に変更
static const char *stream_part = "Content-Type: image/png\r\nContent-Length: %u\r\n\r\n";
#define PART_BOUNDARY "myboundary"
static const char *stream_content_type = "multipart/x-mixed-replace;boundary=--" PART_BOUNDARY;
// ヘッダ設定は変更なし
res = httpd_resp_set_type(req, stream_content_type);
if (res != ESP_OK) {
return res;
}
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
while (true) {
if (canSendImage) {
void *send_data_ptr = NULL;
size_t send_data_len = 0;
// グローバル変数からPNGデータを取得し、送信準備完了フラグをリセット
// 排他制御 (Semaphore/Mutex) が必要だが、ここでは簡単のためグローバル変数を直接操作
send_data_ptr = png_data_ptr;
send_data_len = png_data_len;
png_data_ptr = NULL;
png_data_len = 0;
canSendImage = false; // 送信開始と同時にフラグを下げる
if (send_data_ptr != NULL && send_data_len > 0) {
// 1. 区切りヘッダの送信
static const char *stream_boundary = "\r\n--" PART_BOUNDARY "\r\n";
res = httpd_resp_send_chunk(req, stream_boundary, strlen(stream_boundary));
// 2. PNG画像情報のヘッダ送信
if (res == ESP_OK) {
size_t hlen = snprintf((char *)part_buf, 64, stream_part, send_data_len);
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
}
// 3. PNGデータの送信
if (res == ESP_OK) {
res = httpd_resp_send_chunk(req, (const char *)send_data_ptr, send_data_len);
}
// 4. 送信が完了したらメモリを解放
free(send_data_ptr);
// FPS表示 (表示のためにここで実行)
if (res == ESP_OK) {
float fps = 1000.0 / (millis() - (float)frame_last_time);
Serial.printf("%.02lf(fps) PNG Size: %u bytes\r\n", fps, send_data_len);
frame_last_time = millis();
}
} else {
// データ生成失敗時またはサイズが0の場合
res = ESP_FAIL;
}
if (res != ESP_OK) {
break;
}
}
delay(1);
}
// ループを抜けた場合、未送信のPNGデータがあれば解放
if (png_data_ptr != NULL) {
free(png_data_ptr);
png_data_ptr = NULL;
png_data_len = 0;
}
canSendImage = false; // フラグをリセット
return res;
}
static esp_err_t index_handler(httpd_req_t *req) {
String html_body = "<!DOCTYPE html>\r\n";
html_body += "<html><head></head><body style='text-align:center;'>\r\n";
html_body += "<div style='margin-top: 20px;'>\r\n";
html_body += "<img id='pic_place' width='960' height='960' style='transform:scale(1, 1); image-rendering:pixelated;' crossorigin='anonymous'>\r\n";
html_body += "<canvas id='capture_canvas' width='960' height='960' style='display:none;'></canvas>\r\n";
html_body += "</div>\r\n";
html_body += "<div style='display:flex; justify-content:center; gap:10px; flex-wrap:wrap; margin-top: 10px;'>\r\n";
html_body += "<button style='border-radius:25px; font-size: 64px; padding: 20px 40px;' onclick='startStream()'>START</button>\r\n";
html_body += "<button style='border-radius:25px; font-size: 64px; padding: 20px 40px;' onclick='changeControl(\"re_start_stream\",1)'>RESET</button>\r\n";
html_body += "<button style='border-radius:25px; font-size: 64px; padding: 20px 40px;' onclick='changeControl(\"stop_stream\",1)'>PAUSE</button>\r\n";
html_body += "<button style='border-radius:25px; font-size: 64px; padding: 20px 40px;' onclick='captureAndSave()'>CAPTURE & SAVE</button>\r\n";
html_body += "</div>\r\n";
html_body += "<style>\r\n";
html_body += "#slider1 , #slider2 { width:800px; height: 80px; appearance: none; -webkit-appearance: none; }\r\n";
html_body += "#slider1::-webkit-slider-runnable-track , #slider2::-webkit-slider-runnable-track { height: 80px; background: #ddd; border-radius: 5px; }\r\n";
html_body += "#slider1::-moz-range-track , #slider2::-moz-range-track { height: 80px; background: #ddd; border-radius: 5px; }\r\n";
html_body += "#slider1::-webkit-slider-thumb , #slider2::-webkit-slider-thumb { -webkit-appearance: none; height: 80px; width: 80px; margin-top: 0px; background: #4CAF50; border-radius: 5%; cursor: pointer; }\r\n";
html_body += "#slider1::-moz-range-thumb , #slider2::-moz-range-thumb{ height: 80px; width: 80px; background: #4CAF50; border-radius: 5%; cursor: pointer; }\r\n";
html_body += "</style>\r\n";
html_body += "<div style='margin-top: 20px;'>\r\n";
html_body += "<span id='slider_val1' style='font-size:32px;'>PALLET: 0</span><br>\r\n";
html_body += "<input type='range' min='0' max='7' value='0' step='1' id='slider1' onchange='updateSlider1(this.value)'>\r\n"; // スライダー1
html_body += "</div>\r\n";
html_body += "<div style='margin-top: 20px;'>\r\n";
html_body += "<span id='slider_val2' style='font-size:32px;'>DITHER: 0</span><br>\r\n";
html_body += "<input type='range' min='0' max='7' value='0' step='1' id='slider2' onchange='updateSlider2(this.value)'>\r\n"; // スライダー2
html_body += "<script>\r\n";
html_body += "var base_url = document.location.origin;\r\n";
html_body += "var url_stream = base_url + ':81';\r\n";
html_body += "function startStream() {\r\n";
html_body += "var pic = document.getElementById('pic_place');\r\n";
html_body += "pic.src = url_stream+'/stream';};\r\n";
html_body += "function changeControl(id_txt, value_txt){\r\n";
html_body += "var new_url = base_url+'/command?id=';\r\n";
html_body += "new_url += id_txt + '&';\r\n";
html_body += "new_url += 'value=' + value_txt;\r\n";
html_body += "fetch(new_url, {method: 'GET'})\r\n";
html_body += ".then((response) => {\r\n";
html_body += " if(response.ok){console.log('Command ' + id_txt + ' sent.');}\r\n";
html_body += " else {console.error('Command failed with status: ' + response.status);}\r\n";
html_body += "})\r\n";
html_body += ".catch((error) => console.error('Network error during command send:', error));\r\n";
html_body += "};\r\n";
html_body += "function updateSlider1(value_txt){\r\n";
html_body += "document.getElementById('slider_val1').innerText = 'PALLET: ' + value_txt;\r\n";
html_body += "changeControl('slider1', value_txt);\r\n";
html_body += "};\r\n";
html_body += "function updateSlider2(value_txt){\r\n";
html_body += "document.getElementById('slider_val2').innerText = 'DITHER: ' + value_txt;\r\n";
html_body += "changeControl('slider2', value_txt);\r\n";
html_body += "};\r\n";
html_body += "function captureAndSave() {\r\n";
html_body += " const img = document.getElementById('pic_place');\r\n";
html_body += " const canvas = document.getElementById('capture_canvas');\r\n";
html_body += " if (!img.src || img.src.indexOf('/stream') === -1) {\r\n";
html_body += " alert('Streaming is not started.');\r\n";
html_body += " return;\r\n";
html_body += " }\r\n";
html_body += " try {\r\n";
html_body += " // Get the latest cached image data from the <img> tag and draw it onto the hidden canvas.\r\n";
html_body += " const ctx = canvas.getContext('2d');\r\n";
html_body += " // Set the canvas context to use Nearest-Neighbor scaling for pixelated effect\r\n";
html_body += " ctx.imageSmoothingEnabled = false;\r\n";
html_body += " ctx.webkitImageSmoothingEnabled = false;\r\n";
html_body += " ctx.mozImageSmoothingEnabled = false;\r\n";
html_body += " // Draw the image onto the canvas using the display size (960x960) to preserve scaling.\r\n";
html_body += " ctx.drawImage(img, 0, 0, 960, 960);\r\n";
html_body += " // Get the canvas content as a PNG data URL\r\n";
html_body += " const dataURL = canvas.toDataURL('image/png');\r\n";
html_body += " // Create a temporary link element for download\r\n";
html_body += " const a = document.createElement('a');\r\n";
html_body += " a.href = dataURL;\r\n";
html_body += " // Generate a file name: capture_YYYYMMDD_HHMMSS.png\r\n";
html_body += " const now = new Date();\r\n";
html_body += " const filename = `capture_${now.getFullYear()}${('0'+(now.getMonth()+1)).slice(-2)}${('0'+now.getDate()).slice(-2)}_${('0'+now.getHours()).slice(-2)}${('0'+now.getMinutes()).slice(-2)}${('0'+now.getSeconds()).slice(-2)}.png`;\r\n";
html_body += " a.download = filename;\r\n";
html_body += " // Simulate a click to start the download\r\n";
html_body += " document.body.appendChild(a);\r\n";
html_body += " a.click();\r\n";
html_body += " document.body.removeChild(a);\r\n";
html_body += " console.log(`Image captured and saved as: ${filename}`);\r\n";
html_body += " } catch (error) {\r\n";
html_body += " console.error('Capture failed:', error);\r\n";
html_body += " alert('Failed to capture the image. Please ensure streaming is active.');\r\n";
html_body += " }\r\n";
html_body += "};\r\n";
html_body += "</script></body></html>\r\n";
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Accept-Charset", "UTF-8");
return httpd_resp_send(req, html_body.c_str(), html_body.length());
}
static esp_err_t cmd_handler(httpd_req_t *req) {
char *buf;
size_t buf_len;
char id_txt[32] = {
0,
};
char value_txt[32] = {
0,
};
buf_len = httpd_req_get_url_query_len(req) + 1;
if (buf_len > 1) {
buf = (char *)malloc(buf_len);
if (!buf) {
httpd_resp_send_500(req);
return ESP_FAIL;
}
if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
Serial.println(buf);
if (httpd_query_key_value(buf, "id", id_txt, sizeof(id_txt)) == ESP_OK && httpd_query_key_value(buf, "value", value_txt, sizeof(value_txt)) == ESP_OK) {
} else {
free(buf);
httpd_resp_send_404(req);
return ESP_FAIL;
}
} else {
Serial.println(buf);
free(buf);
httpd_resp_send_404(req);
return ESP_FAIL;
}
free(buf);
} else {
httpd_resp_send_404(req);
return ESP_FAIL;
}
uint8_t val = atoi(value_txt);
int res = 0;
if (!strcmp(id_txt, "re_start_stream")) {
canStartStream = true;
shouldClear = true;
Serial.printf("%s = %d\r\n", id_txt, val);
} else if (!strcmp(id_txt, "stop_stream")) {
canStartStream = false;
if (png_data_ptr != NULL) {
free(png_data_ptr);
png_data_ptr = NULL;
png_data_len = 0;
canSendImage = false;
}
Serial.printf("%s = %d\r\n", id_txt, val);
} else if (!strcmp(id_txt, "slider1")) {
slider_value1 = val;
Serial.printf("%s = %d\r\n", id_txt, slider_value1);
currentPalettelndex = slider_value1;
} else if (!strcmp(id_txt, "slider2")) {
slider_value2 = val;
Serial.printf("%s = %d\r\n", id_txt, slider_value2);
ditherLevels = slider_value2;
} 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 clearAll() {
canvas_565.fillScreen(TFT_BLACK);
canvas_888.fillScreen(TFT_BLACK);
canvas_dihter.fillScreen(TFT_BLACK);
}
void connectToWiFi(const char *ssid, const char *pwd) {
Serial.println("Setting up ESP32 as an Access Point...");
WiFi.mode(WIFI_AP); // Wi-FiモードをAP (Access Point) に設定
if (WiFi.softAP(ssid, password)) {
Serial.println("Access Point started!");
IPAddress myIP = WiFi.softAPIP(); // APのIPアドレスを取得 (通常は 192.168.4.1)
Serial.print("Access Point SSID: ");
Serial.println(ssid);
Serial.print("Access Point IP address: ");
Serial.println(myIP);
delay(1000); // HTTPサーバがIPアドレスをバインドできるように少し待機
} else {
Serial.println("Failed to start Access Point!");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment