/* * ESP32-S3 FREENOVE: Captura foto amb botó BOOT i envia per BLE * Necessita la llibreria ESP32-Camera */ #include #include #include #include #include // Necessitaràs instal·lar aquesta llibreria // Configuració dels pins de la càmera (ajusta segons la teva placa) // Configuració típica per ESP32-S3 amb càmera OV2640 #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define SIOD_GPIO_NUM 4 #define SIOC_GPIO_NUM 5 #define VSYNC_GPIO_NUM 6 #define HREF_GPIO_NUM 7 #define XCLK_GPIO_NUM 15 #define PCLK_GPIO_NUM 13 #define Y9_GPIO_NUM 16 #define Y8_GPIO_NUM 17 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 12 #define Y5_GPIO_NUM 10 #define Y4_GPIO_NUM 8 #define Y3_GPIO_NUM 9 #define Y2_GPIO_NUM 11 #define LED_PIN 43 // LED per indicar captura #define BOOT_BUTTON 0 // Botó BOOT // UUIDs del servei BLE #define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" // Variables globals String deviceName = "ESP32-Camera-9"; BLEServer *pServer = NULL; BLECharacteristic *pCharacteristic = NULL; bool deviceConnected = false; bool oldDeviceConnected = false; bool isCapturing = false; // Configuració de la càmera camera_config_t config; // Variables per al botó bool lastButtonState = HIGH; unsigned long lastDebounceTime = 0; const unsigned long debounceDelay = 50; // Callbacks BLE class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; Serial.println("Dispositiu BLE connectat"); }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; Serial.println("Dispositiu BLE desconnectat"); // Reiniciem la publicitat per permetre noves connexions BLEDevice::startAdvertising(); } }; /* * Versió simplificada - Envia foto per BLE com a text base64 */ void setup() { Serial.begin(115200); Serial.println("\n=== ESP32-S3 FREENOVE Camera BLE (Base64) ==="); pinMode(LED_PIN, OUTPUT); pinMode(BOOT_BUTTON, INPUT_PULLUP); initBLE(); if (!initCamera()) { Serial.println("Error camera!"); while(1) delay(1000); } Serial.println("Sistema llest. Prem BOOT per foto!"); } void loop() { // Gestió connexió BLE if (!deviceConnected && oldDeviceConnected) { delay(500); pServer->startAdvertising(); oldDeviceConnected = deviceConnected; } if (deviceConnected && !oldDeviceConnected) { oldDeviceConnected = deviceConnected; } // Botó BOOT if (digitalRead(BOOT_BUTTON) == LOW && !isCapturing) { delay(50); // Debounce simple if (digitalRead(BOOT_BUTTON) == LOW) { takeAndSendPhotoBase64(); } } delay(10); } // Funció per inicialitzar BLE void initBLE() { BLEDevice::init(deviceName); pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_INDICATE ); pService->start(); // Configurar publicitat BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->setScanResponse(true); pAdvertising->setMinPreferred(0x06); // funcions que ajuden amb iPhone pAdvertising->setMinPreferred(0x12); BLEDevice::startAdvertising(); Serial.println("BLE inicialitzat. Nom del dispositiu: ESP32-Camera"); Serial.println("Connecta't des del teu dispositiu BLE"); } // Funció per inicialitzar la càmera bool initCamera() { 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; // Qualitat de la imatge (ajusta segons la memòria disponible) config.frame_size = FRAMESIZE_SVGA; // 800x600 config.jpeg_quality = 10; // 0-63 (més baix = millor qualitat) config.fb_count = 1; esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Error inicialitzant càmera: 0x%x", err); return false; } // Configuració addicional de la càmera sensor_t *s = esp_camera_sensor_get(); if (s != NULL) { // Ajustos automàtics s->set_brightness(s, 0); // -2 to 2 s->set_contrast(s, 0); // -2 to 2 s->set_saturation(s, 0); // -2 to 2 s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect) s->set_whitebal(s, 1); // 0 = disable, 1 = enable s->set_awb_gain(s, 1); // 0 = disable, 1 = enable s->set_wb_mode(s, 0); // 0 to 4 s->set_exposure_ctrl(s, 1); // 0 = disable, 1 = enable s->set_aec2(s, 0); // 0 = disable, 1 = enable s->set_ae_level(s, 0); // -2 to 2 s->set_aec_value(s, 1000); // 0 to 1200 s->set_gain_ctrl(s, 1); // 0 = disable, 1 = enable s->set_agc_gain(s, 5); // 0 to 30 s->set_gainceiling(s, (gainceiling_t)6); // 0 to 6 s->set_bpc(s, 0); // 0 = disable, 1 = enable s->set_wpc(s, 1); // 0 = disable, 1 = enable s->set_raw_gma(s, 1); // 0 = disable, 1 = enable s->set_lenc(s, 1); // 0 = disable, 1 = enable s->set_hmirror(s, 0); // 0 = disable, 1 = enable s->set_vflip(s, 1); // 0 = disable, 1 = enable s->set_dcw(s, 1); // 0 = disable, 1 = enable s->set_colorbar(s, 0); // 0 = disable, 1 = enable } Serial.println("Càmera inicialitzada correctament"); return true; } // Funció per capturar i enviar com base64 void takeAndSendPhotoBase64() { if (!deviceConnected) { Serial.println("Connecta primer un dispositiu BLE"); return; } Serial.println("Capturant foto..."); isCapturing = true; digitalWrite(LED_PIN, HIGH); // Capturar camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Error captura"); digitalWrite(LED_PIN, LOW); isCapturing = false; return; } Serial.printf("Foto: %d bytes\n", fb->len); // Convertir a base64 String base64Image = base64::encode(fb->buf, fb->len); Serial.printf("Base64: %d caràcters\n", base64Image.length()); // Enviar per BLE (fragmentat) sendBase64BLE(base64Image); // Alliberar esp_camera_fb_return(fb); digitalWrite(LED_PIN, LOW); isCapturing = false; Serial.println("Foto enviada!"); } // Enviar base64 per BLE fragmentat void sendBase64BLE(String &base64Data) { const int CHUNK_SIZE = 100; // Caràcters per paquet // Enviar mida total uint32_t totalSize = base64Data.length(); pCharacteristic->setValue((uint8_t*)&totalSize, sizeof(totalSize)); pCharacteristic->notify(); delay(20); // Enviar fragmentat for (size_t i = 0; i < base64Data.length(); i += CHUNK_SIZE) { String chunk = base64Data.substring(i, min(i + CHUNK_SIZE, base64Data.length())); pCharacteristic->setValue(chunk.c_str()); pCharacteristic->notify(); delay(20); // Pausa per evitar sobrecàrrega if (i % 1000 == 0 || i + CHUNK_SIZE >= base64Data.length()) { int percent = (i * 100) / base64Data.length(); Serial.printf("Enviant BLE: %d%%\n", percent); } } // Marcar final //pCharacteristic->setValue("END"); //pCharacteristic->notify(); }