#1 25-01-2022 23:53:01

IvanAltay
Administrator
Зарегистрирован: 03-05-2018
Сообщений: 4,586

ESP32CAM + tft ST7735S.

Подключение.

16431294471291927634640045519286.jpg

16431294904851346982994729850836.jpg

1643129523847180585997545427538.jpg

Не в сети

#2 25-01-2022 23:54:22

IvanAltay
Administrator
Зарегистрирован: 03-05-2018
Сообщений: 4,586

Re: ESP32CAM + tft ST7735S.

16431296077341863088720540963497.jpg

Не в сети

#3 26-01-2022 00:01:31

IvanAltay
Administrator
Зарегистрирован: 03-05-2018
Сообщений: 4,586

Re: ESP32CAM + tft ST7735S.

Тестовый код.

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <SPI.h>
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS   15  // Chip select control pin
#define TFT_DC    2  // Data Command control pin
#define TFT_RST   12
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
float p = 3.1415926;
void setup(void) {
  Serial.begin(9600);
  Serial.print(F("Hello! ST77xx TFT Test"));
  // Use this initializer if using a 1.8" TFT screen:
  tft.initR(INITR_BLACKTAB);      // Init ST7735S chip, black tab
  Serial.println(F("Initialized"));
  uint16_t time = millis();
  tft.fillScreen(ST77XX_BLACK);
  time = millis() - time;
  Serial.println(time, DEC);
  delay(500);
  // large block of text
  tft.fillScreen(ST77XX_BLACK);
  testdrawtext("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur adipiscing ante sed nibh tincidunt feugiat. Maecenas enim massa, fringilla sed malesuada et, malesuada sit amet turpis. Sed porttitor neque ut ante pretium vitae malesuada nunc bibendum. Nullam aliquet ultrices massa eu hendrerit. Ut sed nisi lorem. In vestibulum purus a tortor imperdiet posuere. ", ST77XX_WHITE);
  delay(1000);
  // tft print function!
  tftPrintTest();
  delay(4000);
  // a single pixel
  tft.drawPixel(tft.width()/2, tft.height()/2, ST77XX_GREEN);
  delay(500);
  // line draw test
  testlines(ST77XX_YELLOW);
  delay(500);
  // optimized lines
  testfastlines(ST77XX_RED, ST77XX_BLUE);
  delay(500);
  testdrawrects(ST77XX_GREEN);
  delay(500);
  testfillrects(ST77XX_YELLOW, ST77XX_MAGENTA);
  delay(500);
  tft.fillScreen(ST77XX_BLACK);
  testfillcircles(10, ST77XX_BLUE);
  testdrawcircles(10, ST77XX_WHITE);
  delay(500);
  testroundrects();
  delay(500);
  testtriangles();
  delay(500);
  mediabuttons();
  delay(500);
  Serial.println("done");
  delay(1000);
}
void loop() {
  tft.invertDisplay(true);
  delay(500);
  tft.invertDisplay(false);
  delay(500);
}
void testlines(uint16_t color) {
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=0; x < tft.width(); x+=6) {
    tft.drawLine(0, 0, x, tft.height()-1, color);
    delay(0);
  }
  for (int16_t y=0; y < tft.height(); y+=6) {
    tft.drawLine(0, 0, tft.width()-1, y, color);
    delay(0);
  }
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=0; x < tft.width(); x+=6) {
    tft.drawLine(tft.width()-1, 0, x, tft.height()-1, color);
    delay(0);
  }
  for (int16_t y=0; y < tft.height(); y+=6) {
    tft.drawLine(tft.width()-1, 0, 0, y, color);
    delay(0);
  }
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=0; x < tft.width(); x+=6) {
    tft.drawLine(0, tft.height()-1, x, 0, color);
    delay(0);
  }
  for (int16_t y=0; y < tft.height(); y+=6) {
    tft.drawLine(0, tft.height()-1, tft.width()-1, y, color);
    delay(0);
  }
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=0; x < tft.width(); x+=6) {
    tft.drawLine(tft.width()-1, tft.height()-1, x, 0, color);
    delay(0);
  }
  for (int16_t y=0; y < tft.height(); y+=6) {
    tft.drawLine(tft.width()-1, tft.height()-1, 0, y, color);
    delay(0);
  }
}
void testdrawtext(char *text, uint16_t color) {
  tft.setCursor(0, 0);
  tft.setTextColor(color);
  tft.setTextWrap(true);
  tft.print(text);
}
void testfastlines(uint16_t color1, uint16_t color2) {
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t y=0; y < tft.height(); y+=5) {
    tft.drawFastHLine(0, y, tft.width(), color1);
  }
  for (int16_t x=0; x < tft.width(); x+=5) {
    tft.drawFastVLine(x, 0, tft.height(), color2);
  }
}
void testdrawrects(uint16_t color) {
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=0; x < tft.width(); x+=6) {
    tft.drawRect(tft.width()/2 -x/2, tft.height()/2 -x/2 , x, x, color);
  }
}
void testfillrects(uint16_t color1, uint16_t color2) {
  tft.fillScreen(ST77XX_BLACK);
  for (int16_t x=tft.width()-1; x > 6; x-=6) {
    tft.fillRect(tft.width()/2 -x/2, tft.height()/2 -x/2 , x, x, color1);
    tft.drawRect(tft.width()/2 -x/2, tft.height()/2 -x/2 , x, x, color2);
  }
}
void testfillcircles(uint8_t radius, uint16_t color) {
  for (int16_t x=radius; x < tft.width(); x+=radius*2) {
    for (int16_t y=radius; y < tft.height(); y+=radius*2) {
      tft.fillCircle(x, y, radius, color);
    }
  }
}
void testdrawcircles(uint8_t radius, uint16_t color) {
  for (int16_t x=0; x < tft.width()+radius; x+=radius*2) {
    for (int16_t y=0; y < tft.height()+radius; y+=radius*2) {
      tft.drawCircle(x, y, radius, color);
    }
  }
}
void testtriangles() {
  tft.fillScreen(ST77XX_BLACK);
  uint16_t color = 0xF800;
  int t;
  int w = tft.width()/2;
  int x = tft.height()-1;
  int y = 0;
  int z = tft.width();
  for(t = 0 ; t <= 15; t++) {
    tft.drawTriangle(w, y, y, x, z, x, color);
    x-=4;
    y+=4;
    z-=4;
    color+=100;
  }
}
void testroundrects() {
  tft.fillScreen(ST77XX_BLACK);
  uint16_t color = 100;
  int i;
  int t;
  for(t = 0 ; t <= 4; t+=1) {
    int x = 0;
    int y = 0;
    int w = tft.width()-2;
    int h = tft.height()-2;
    for(i = 0 ; i <= 16; i+=1) {
      tft.drawRoundRect(x, y, w, h, 5, color);
      x+=2;
      y+=3;
      w-=4;
      h-=6;
      color+=1100;
    }
    color+=100;
  }
}
void tftPrintTest() {
  tft.setTextWrap(false);
  tft.fillScreen(ST77XX_BLACK);
  tft.setCursor(0, 30);
  tft.setTextColor(ST77XX_RED);
  tft.setTextSize(1);
  tft.println("Hello World!");
  tft.setTextColor(ST77XX_YELLOW);
  tft.setTextSize(2);
  tft.println("Hello World!");
  tft.setTextColor(ST77XX_GREEN);
  tft.setTextSize(3);
  tft.println("Hello World!");
  tft.setTextColor(ST77XX_BLUE);
  tft.setTextSize(4);
  tft.print(1234.567);
  delay(1500);
  tft.setCursor(0, 0);
  tft.fillScreen(ST77XX_BLACK);
  tft.setTextColor(ST77XX_WHITE);
  tft.setTextSize(0);
  tft.println("Hello World!");
  tft.setTextSize(1);
  tft.setTextColor(ST77XX_GREEN);
  tft.print(p, 6);
  tft.println(" Want pi?");
  tft.println(" ");
  tft.print(8675309, HEX); // print 8,675,309 out in HEX!
  tft.println(" Print HEX!");
  tft.println(" ");
  tft.setTextColor(ST77XX_WHITE);
  tft.println("Sketch has been");
  tft.println("running for: ");
  tft.setTextColor(ST77XX_MAGENTA);
  tft.print(millis() / 1000);
  tft.setTextColor(ST77XX_WHITE);
  tft.print(" seconds.");
}
void mediabuttons() {
  // play
  tft.fillScreen(ST77XX_BLACK);
  tft.fillRoundRect(25, 10, 78, 60, 8, ST77XX_WHITE);
  tft.fillTriangle(42, 20, 42, 60, 90, 40, ST77XX_RED);
  delay(500);
  // pause
  tft.fillRoundRect(25, 90, 78, 60, 8, ST77XX_WHITE);
  tft.fillRoundRect(39, 98, 20, 45, 5, ST77XX_GREEN);
  tft.fillRoundRect(69, 98, 20, 45, 5, ST77XX_GREEN);
  delay(500);
  // play color
  tft.fillTriangle(42, 20, 42, 60, 90, 40, ST77XX_BLUE);
  delay(50);
  // pause color
  tft.fillRoundRect(39, 98, 20, 45, 5, ST77XX_RED);
  tft.fillRoundRect(69, 98, 20, 45, 5, ST77XX_RED);
  // play color
  tft.fillTriangle(42, 20, 42, 60, 90, 40, ST77XX_GREEN);
}

Не в сети

#4 26-01-2022 00:06:11

IvanAltay
Administrator
Зарегистрирован: 03-05-2018
Сообщений: 4,586

Re: ESP32CAM + tft ST7735S.

Ноги дисплея VDD и BLK соединяем вместе и они идут на питание +3.3В
Ещё кусок кода подпишу, чтоб понятней было подключение дисплея.

#define TFT_MOSI 13//SDA
#define TFT_SCLK 14//SCL
#define TFT_CS    15//CS  Chip select control pin
#define TFT_DC     2//DC Data Command control pin
#define TFT_RST  12//RST
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

Не в сети

#5 26-01-2022 02:56:47

IvanAltay
Administrator
Зарегистрирован: 03-05-2018
Сообщений: 4,586

Re: ESP32CAM + tft ST7735S.

Разрешение картинки 160/120 пикселей.
16431405032693324100719968280078.jpg

16431405451957677351281764460162.jpg
16431410933713683923707035274358.jpg

16431411490281521648399276683510.jpg

Не в сети

#6 26-01-2022 20:59:32

IvanAltay
Administrator
Зарегистрирован: 03-05-2018
Сообщений: 4,586

Re: ESP32CAM + tft ST7735S.

Дальше, код огромный и в основном не мой. Я туда привинтил только обработку дисплея ST8266S. Исходник тут https://github.com/alanesq/esp32cam-demo

Не в сети

#7 26-01-2022 21:09:49

IvanAltay
Administrator
Зарегистрирован: 03-05-2018
Сообщений: 4,586

Re: ESP32CAM + tft ST7735S.

Камера делает снимок каждые 20 секунд и выводит изображение на экран.

#include <Arduino.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#define TFT_MOSI 13//SDA
#define TFT_SCLK 14//SCL
#define TFT_CS   15//CS  Chip select control pin
#define TFT_DC    2//DC Data Command control pin
#define TFT_RST  12//RST
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
#include "esp_camera.h"         // https://github.com/espressif/esp32-camera
//   ---------------------------------------------------------------------------------------------------------
 #define SSID_NAME "*******"
 #define SSID_PASWORD "******"
//   ---------------------------------------------------------------------------------------------------------
 #include <Arduino.h>
 // forward declarations
   bool initialiseCamera();
   bool cameraImageSettings();
   String localTime();
   void flashLED(int reps);
   byte storeImage();
   void handleRoot();
   void handlePhoto();
   bool handleImg();
   void handleNotFound();
   void readRGBImage();
   bool getNTPtime(int sec);
   bool handleJPG();
   void handleStream();
   int requestWebPage(String*, String*, int);
   void handleTest();
   void brightLed(byte ledBrightness);
   void setupFlashPWM();
   void changeResolution(framesize_t);
   void handleData();

// ---------------------------------------------------------------
//                           -SETTINGS
// ---------------------------------------------------------------

 const char* stitle = "ESP32Cam-demo";                  // title of this sketch
 const char* sversion = "25Jan22";                      // Sketch version

 bool sendRGBfile = 0;                                  // if set '/rgb' will just return raw rgb data which can be saved as a file rather than display a HTML pag

 uint16_t datarefresh = 2200;                           // how often to refresh data on root web page (ms)
 uint16_t imagerefresh = 5000;                          // how often to refresh the image on root web page (ms)

 const bool serialDebug = 1;                            // show debug info. on serial port (1=enabled, disable if using pins 1 and 3 as gpio)

 #define useMCP23017 0                                  // if MCP23017 IO expander chip is being used (on pins 12 and 13)

 // Camera related
   bool flashRequired = 1;                              // If flash to be used when capturing image (1 = yes)
   framesize_t FRAME_SIZE_IMAGE = FRAMESIZE_QQVGA;        // Image resolution:
                                                        //               default = "const framesize_t FRAME_SIZE_IMAGE = FRAMESIZE_VGA"
                                                        //               160x120 (QQVGA), 128x160 (QQVGA2), 176x144 (QCIF), 240x176 (HQVGA),
                                                        //               320x240 (QVGA), 400x296 (CIF), 640x480 (VGA, default), 800x600 (SVGA),
                                                        //               1024x768 (XGA), 1280x1024 (SXGA), 1600x1200 (UXGA)
   #define PIXFORMAT PIXFORMAT_JPEG;                    // image format, Options =  YUV422, GRAYSCALE, RGB565, JPEG, RGB888
   int cameraImageExposure = 0;                         // Camera exposure (0 - 1200)   If gain and exposure both set to zero then auto adjust is enabled
   int cameraImageGain = 0;                             // Image gain (0 - 30)
   int cameraImageBrightness = 0;                       // Image brightness (-2 to +2)

 const int TimeBetweenStatus = 600;                     // speed of flashing system running ok status light (milliseconds)

 const int indicatorLED = 33;                           // onboard small LED pin (33)

 // Bright LED (Flash)
   const int brightLED = 4;                             // onboard Illumination/flash LED pin (4)
   int brightLEDbrightness = 0;                         // initial brightness (0 - 255)
   const int ledFreq = 5000;                            // PWM settings
   const int ledChannel = 15;                           // camera uses timer1
   const int ledRresolution = 8;                        // resolution (8 = from 0 to 255)

 const int iopinA = 13;                                 // general io pin 13
 const int iopinB = 12;                                 // general io pin 12 (must not be high at boot)

 const int serialSpeed = 115200;                        // Serial data speed to use

 // NTP - Internet time
   const char* ntpServer = "pool.ntp.org";
   const char* TZ_INFO    = "GMT+0BST-1,M3.5.0/01:00:00,M10.5.0/02:00:00";  // enter your time zone (https://remotemonitoringsystems.ca/time-zone-abbreviations.php)
   long unsigned lastNTPtime;
   tm timeinfo;
   time_t now;

// camera settings (for the standard - OV2640 - CAMERA_MODEL_AI_THINKER)
// see: https://randomnerdtutorials.com/esp32-cam-camera-pin-gpios/
// set camera resolution etc. in 'initialiseCamera()' and 'cameraImageSettings()'
 #define CAMERA_MODEL_AI_THINKER
 #define PWDN_GPIO_NUM     32      // power to camera (on/off)
 #define RESET_GPIO_NUM    -1      // -1 = not used
 #define XCLK_GPIO_NUM      0
 #define SIOD_GPIO_NUM     26      // i2c sda
 #define SIOC_GPIO_NUM     27      // i2c scl
 #define Y9_GPIO_NUM       35
 #define Y8_GPIO_NUM       34
 #define Y7_GPIO_NUM       39
 #define Y6_GPIO_NUM       36
 #define Y5_GPIO_NUM       21
 #define Y4_GPIO_NUM       19
 #define Y3_GPIO_NUM       18
 #define Y2_GPIO_NUM        5
 #define VSYNC_GPIO_NUM    25      // vsync_pin
 #define HREF_GPIO_NUM     23      // href_pin
 #define PCLK_GPIO_NUM     22      // pixel_clock_pin



// ******************************************************************************************************************

//#include "esp_camera.h"         // https://github.com/espressif/esp32-camera
// #include "camera_pins.h"
#include <base64.h>             // for encoding buffer to display image on page
#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include "driver/ledc.h"        // used to configure pwm on illumination led

// spiffs used to store images if no sd card present
 #include <SPIFFS.h>
 #include <FS.h>                // gives file access on spiffs

WebServer server(80);           // serve web pages on port 80

// Used to disable brownout detection
 #include "soc/soc.h"
 #include "soc/rtc_cntl_reg.h"

// sd-card
 #include "SD_MMC.h"                         // sd card - see https://randomnerdtutorials.com/esp32-cam-take-photo-save-microsd-card/
 #include <SPI.h>
 #include <FS.h>                             // gives file access
 #define SD_CS 5                             // sd chip select pin = 5

// MCP23017 IO expander on pins 12 and 13 (optional)
 #if useMCP23017 == 1
   #include <Wire.h>
   #include "Adafruit_MCP23017.h"
   Adafruit_MCP23017 mcp;
   // Wire.setClock(1700000); // set frequency to 1.7mhz
 #endif

// Define some global variables:
 uint32_t lastStatus = millis();           // last time status light changed status (to flash all ok led)
 bool sdcardPresent;                       // flag if an sd card is detected
 int imageCounter;                         // image file name on sd card counter
 String spiffsFilename = "/image.jpg";     // image name to use when storing in spiffs
 String ImageResDetails = "Unknown";       // image resolution info
// ******************************************************************************************************************
// ---------------------------------------------------------------
//    -SETUP     SETUP     SETUP     SETUP     SETUP     SETUP
// ---------------------------------------------------------------
void setup() {
 if (serialDebug) {
   Serial.begin(serialSpeed);                     // Start serial communication
   // Serial.setDebugOutput(true);

   Serial.println("\n\n\n");                      // line feeds
   Serial.println("-----------------------------------");
   Serial.printf("Starting - %s - %s \n", stitle, sversion);
   Serial.println("-----------------------------------");
   // Serial.print("Reset reason: " + ESP.getResetReason());
 }

 WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);     // Turn-off the 'brownout detector'

 // small indicator led on rear of esp32cam board
   pinMode(indicatorLED, OUTPUT);
   digitalWrite(indicatorLED,HIGH);

 // Connect to wifi
   digitalWrite(indicatorLED,LOW);               // small indicator led on
   if (serialDebug) {
     Serial.print("\nConnecting to ");
     Serial.print(SSID_NAME);
     Serial.print("\n   ");
   }
   WiFi.begin(SSID_NAME, SSID_PASWORD);
   while (WiFi.status() != WL_CONNECTED) {
       delay(500);
       if (serialDebug) Serial.print(".");
   }
   if (serialDebug) {
     Serial.print("\nWiFi connected, ");
     Serial.print("IP address: ");
     Serial.println(WiFi.localIP());
   }
   server.begin();                               // start web server
   digitalWrite(indicatorLED,HIGH);              // small indicator led off

 // define the web pages (i.e. call these procedures when url is requested)
   server.on("/", handleRoot);                   // root page
   server.on("/data", handleData);               // suplies data to periodically update root (AJAX)
   server.on("/jpg", handleJPG);                 // capture image and send as jpg
   server.on("/stream", handleStream);           // stream live video
   server.on("/photo", handlePhoto);             // save image to sd card
   server.on("/img", handleImg);                 // show image from sd card
   server.on("/rgb", readRGBImage);              // demo converting image to RGB
   server.on("/test", handleTest);               // Testing procedure
   server.onNotFound(handleNotFound);            // invalid url requested

 // NTP - internet time
   if (serialDebug) Serial.println("\nGetting real time (NTP)");
   configTime(0, 0, ntpServer);
   setenv("TZ", TZ_INFO, 1);
   if (getNTPtime(10)) {  // wait up to 10 sec to sync
   } else {
     if (serialDebug) Serial.println("Time not set");
   }
   lastNTPtime = time(&now);

 // set up camera
     if (serialDebug) Serial.print(("\nInitialising camera: "));
     if (initialiseCamera()) {
       if (serialDebug) Serial.println("OK");
     }
     else {
       if (serialDebug) Serial.println("failed");
     }

 // Spiffs - for storing images without an sd card
 //       see: https://circuits4you.com/2018/01/31/example-of-esp8266-flash-file-system-spiffs/
   if (!SPIFFS.begin(true)) {
     if (serialDebug) Serial.println(("An Error has occurred while mounting SPIFFS - restarting"));
     delay(5000);
     ESP.restart();                               // restart and try again
     delay(5000);
   } else {
     // SPIFFS.format();      // wipe spiffs
     if (serialDebug) {
       Serial.print(("SPIFFS mounted successfully: "));
       Serial.printf("total bytes: %d , used: %d \n", SPIFFS.totalBytes(), SPIFFS.usedBytes());
     }
   }

 // SD Card - if one is detected set 'sdcardPresent' High
     if (!SD_MMC.begin("/sdcard", true)) {        // if loading sd card fails
       // note: ('/sdcard", true)' = 1bit mode - see: https://www.reddit.com/r/esp32/comments/d71es9/a_breakdown_of_my_experience_trying_to_talk_to_an/
       if (serialDebug) Serial.println("No SD Card detected");
       sdcardPresent = 0;                        // flag no sd card available
     } else {
       uint8_t cardType = SD_MMC.cardType();
       if (cardType == CARD_NONE) {              // if invalid card found
           if (serialDebug) Serial.println("SD Card type detect failed");
           sdcardPresent = 0;                    // flag no sd card available
       } else {
         // valid sd card detected
         uint16_t SDfreeSpace = (uint64_t)(SD_MMC.totalBytes() - SD_MMC.usedBytes()) / (1024 * 1024);
         if (serialDebug) Serial.printf("SD Card found, free space = %dMB \n", SDfreeSpace);
         sdcardPresent = 1;                      // flag sd card available
       }
     }
     fs::FS &fs = SD_MMC;                        // sd card file system

 // discover the number of image files already stored in '/img' folder of the sd card and set image file counter accordingly
   imageCounter = 0;
   if (sdcardPresent) {
     int tq=fs.mkdir("/img");                    // create the '/img' folder on sd card (in case it is not already there)
     if (!tq) {
       if (serialDebug) Serial.println("Unable to create IMG folder on sd card");
     }

     // open the image folder and step through all files in it
       File root = fs.open("/img");
       while (true)
       {
           File entry =  root.openNextFile();    // open next file in the folder
           if (!entry) break;                    // if no more files in the folder
           imageCounter ++;                      // increment image counter
           entry.close();
       }
       root.close();
       if (serialDebug) Serial.printf("Image file count = %d \n",imageCounter);
   }

 // define i/o pins
   pinMode(indicatorLED, OUTPUT);            // defined again as sd card config can reset it
   digitalWrite(indicatorLED,HIGH);          // led off = High
   pinMode(iopinA, INPUT);                   // pin 13 - free io pin, can be used for input or output
   pinMode(iopinB, OUTPUT);                  // pin 12 - free io pin, can be used for input or output (must not be high at boot)

 // MCP23017 io expander (requires adafruit MCP23017 library)
 #if useMCP23017 == 1
   Wire.begin(12,13);             // use pins 12 and 13 for i2c
   mcp.begin(&Wire);              // use default address 0
   mcp.pinMode(0, OUTPUT);        // Define GPA0 (physical pin 21) as output pin
   mcp.pinMode(8, INPUT);         // Define GPB0 (physical pin 1) as input pin
   mcp.pullUp(8, HIGH);           // turn on a 100K pullup internally
   // change pin state with   mcp.digitalWrite(0, HIGH);
   // read pin state with     mcp.digitalRead(8)
 #endif

setupFlashPWM();    // configure PWM for the illumination LED

 // startup complete
   if (serialDebug) Serial.println("\nStarted...");
   flashLED(2);     // flash the onboard indicator led
   brightLed(64);    // change bright LED
   delay(200);
   brightLed(0);    // change bright LED
/////////////////TFT////////////////
tft.initR(INITR_BLACKTAB);  
tft.setRotation(1);
 tft.fillScreen(ST77XX_BLACK);
 tft.drawPixel(tft.width()/2, tft.height()/2,tft.color565(0,255,0));
}  // setup


// ******************************************************************************************************************


// ----------------------------------------------------------------
//   -LOOP     LOOP     LOOP     LOOP     LOOP     LOOP     LOOP
// ----------------------------------------------------------------


void loop() {

 server.handleClient();          // handle any incoming web page requests






 //                           <<< YOUR CODE HERE >>>






//  //  Capture an image and save to sd card every 20 seconds (i.e. time lapse)
      static uint32_t lastCamera = millis();
      if ( ((unsigned long)(millis() - lastCamera) >= 20000) && sdcardPresent ) {
        lastCamera = millis();     // reset timer
        storeImage();              // save an image to sd card
    readRGBImage();
 
     }



}  // loop



// ******************************************************************************************************************


// ----------------------------------------------------------------
//                        Initialise the camera
// ----------------------------------------------------------------
// returns TRUE if successful

bool initialiseCamera() {

   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_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;               // XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental)
   config.pixel_format = PIXFORMAT;              // Options =  YUV422, GRAYSCALE, RGB565, JPEG, RGB888
   config.frame_size = FRAME_SIZE_IMAGE;         // Image sizes: 160x120 (QQVGA), 128x160 (QQVGA2), 176x144 (QCIF), 240x176 (HQVGA), 320x240 (QVGA),
                                                 //              400x296 (CIF), 640x480 (VGA, default), 800x600 (SVGA), 1024x768 (XGA), 1280x1024 (SXGA),
                                                 //              1600x1200 (UXGA)
   config.jpeg_quality = 10;                     // 0-63 lower number means higher quality
   config.fb_count = 1;                          // if more than one, i2s runs in continuous mode. Use only with JPEG

   // check the esp32cam board has a psram chip installed (extra memory used for storing captured images)
   //    Note: if not using "AI thinker esp32 cam" in the Arduino IDE, SPIFFS must be enabled
   if (!psramFound()) {
     if (serialDebug) Serial.println("Warning: No PSRam found so defaulting to image size 'CIF'");
     config.frame_size = FRAMESIZE_CIF;
   }

   //#if defined(CAMERA_MODEL_ESP_EYE)
   //  pinMode(13, INPUT_PULLUP);
   //  pinMode(14, INPUT_PULLUP);
   //#endif

   esp_err_t camerr = esp_camera_init(&config);  // initialise the camera
   if (camerr != ESP_OK) {
     if (serialDebug) Serial.printf("ERROR: Camera init failed with error 0x%x", camerr);
   }

   cameraImageSettings();                        // apply custom camera settings

   return (camerr == ESP_OK);                    // return boolean result of camera initialisation
}


// ******************************************************************************************************************


// ----------------------------------------------------------------
//                   -Change camera image settings
// ----------------------------------------------------------------
// Adjust image properties (brightness etc.)
// Defaults to auto adjustments if exposure and gain are both set to zero
// - Returns TRUE if successful
// BTW - some interesting info on exposure times here: https://github.com/raduprv/esp32-cam_ov2640-timelapse

bool cameraImageSettings() {

  if (serialDebug) Serial.println("Applying camera settings");

   sensor_t *s = esp_camera_sensor_get();
   // something to try?:     if (s->id.PID == OV3660_PID)
   if (s == NULL) {
     if (serialDebug) Serial.println("Error: problem reading camera sensor settings");
     return 0;
   }

   // if both set to zero enable auto adjust
   if (cameraImageExposure == 0 && cameraImageGain == 0) {
     // enable auto adjust
       s->set_gain_ctrl(s, 1);                       // auto gain on
       s->set_exposure_ctrl(s, 1);                   // auto exposure on
       s->set_awb_gain(s, 1);                        // Auto White Balance enable (0 or 1)
       s->set_brightness(s, cameraImageBrightness);  // (-2 to 2) - set brightness
   } else {
     // Apply manual settings
       s->set_gain_ctrl(s, 0);                       // auto gain off
       s->set_awb_gain(s, 1);                        // Auto White Balance enable (0 or 1)
       s->set_exposure_ctrl(s, 0);                   // auto exposure off
       s->set_brightness(s, cameraImageBrightness);  // (-2 to 2) - set brightness
       s->set_agc_gain(s, cameraImageGain);          // set gain manually (0 - 30)
       s->set_aec_value(s, cameraImageExposure);     // set exposure manually  (0-1200)
   }

   return 1;
}  // cameraImageSettings


//    // More camera settings available:
//    // If you enable gain_ctrl or exposure_ctrl it will prevent a lot of the other settings having any effect
//    // more info on settings here: https://randomnerdtutorials.com/esp32-cam-ov2640-camera-settings/
//    s->set_gain_ctrl(s, 0);                       // auto gain off (1 or 0)
//    s->set_exposure_ctrl(s, 0);                   // auto exposure off (1 or 0)
//    s->set_agc_gain(s, cameraImageGain);          // set gain manually (0 - 30)
//    s->set_aec_value(s, cameraImageExposure);     // set exposure manually  (0-1200)
//    s->set_vflip(s, cameraImageInvert);           // Invert image (0 or 1)
//    s->set_quality(s, 10);                        // (0 - 63)
//    s->set_gainceiling(s, GAINCEILING_32X);       // Image gain (GAINCEILING_x2, x4, x8, x16, x32, x64 or x128)
//    s->set_brightness(s, cameraImageBrightness);  // (-2 to 2) - set brightness
//    s->set_lenc(s, 1);                            // lens correction? (1 or 0)
//    s->set_saturation(s, 0);                      // (-2 to 2)
//    s->set_contrast(s, cameraImageContrast);      // (-2 to 2)
//    s->set_sharpness(s, 0);                       // (-2 to 2)
//    s->set_hmirror(s, 0);                         // (0 or 1) flip horizontally
//    s->set_colorbar(s, 0);                        // (0 or 1) - show a testcard
//    s->set_special_effect(s, 0);                  // (0 to 6?) apply special effect
//    s->set_whitebal(s, 0);                        // white balance enable (0 or 1)
//    s->set_awb_gain(s, 1);                        // Auto White Balance enable (0 or 1)
//    s->set_wb_mode(s, 0);                         // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
//    s->set_dcw(s, 0);                             // downsize enable? (1 or 0)?
//    s->set_raw_gma(s, 1);                         // (1 or 0)
//    s->set_aec2(s, 0);                            // automatic exposure sensor?  (0 or 1)
//    s->set_ae_level(s, 0);                        // auto exposure levels (-2 to 2)
//    s->set_bpc(s, 0);                             // black pixel correction
//    s->set_wpc(s, 0);                             // white pixel correction


// ******************************************************************************************************************


//                          Misc small procedures


// ----------------------------------------------------------------
//       set up PWM for the illumination LED (flash)
// ----------------------------------------------------------------
// note: I am not sure PWM is very reliable on the esp32cam - requires more testing
void setupFlashPWM() {
    ledcSetup(ledChannel, ledFreq, ledRresolution);
    ledcAttachPin(brightLED, ledChannel);
    brightLed(brightLEDbrightness);
}


// change illumination LED brightness
 void brightLed(byte ledBrightness){
   brightLEDbrightness = ledBrightness;    // store setting
   ledcWrite(ledChannel, ledBrightness);   // change LED brightness (0 - 255)
   if (serialDebug) Serial.println("Brightness changed to " + String(ledBrightness) );
 }


// ----------------------------------------------------------------
//          returns the current real time as a String
// ----------------------------------------------------------------
//   see: https://randomnerdtutorials.com/esp32-date-time-ntp-client-server-arduino/
String localTime() {
 struct tm timeinfo;
 char ttime[40];
 if(!getLocalTime(&timeinfo)) return"Failed to obtain time";
 strftime(ttime,40,  "%A %B %d %Y %H:%M:%S", &timeinfo);
 return ttime;
}


// ----------------------------------------------------------------
//        flash the indicator led 'reps' number of times
// ----------------------------------------------------------------
void flashLED(int reps) {
 for(int x=0; x < reps; x++) {
   digitalWrite(indicatorLED,LOW);
   delay(1000);
   digitalWrite(indicatorLED,HIGH);
   delay(500);
 }
}


// ----------------------------------------------------------------
//      send standard html header (i.e. start of web page)
// ----------------------------------------------------------------
void sendHeader(WiFiClient &client, char* hTitle) {
    // Start page
      client.write("HTTP/1.1 200 OK\r\n");
      client.write("Content-Type: text/html\r\n");
      client.write("Connection: close\r\n");
      client.write("\r\n");
      client.write("<!DOCTYPE HTML><html lang='en'>\n");
    // HTML / CSS
      client.printf(R"=====(
        <head>
          <meta name='viewport' content='width=device-width, initial-scale=1.0'>
          <title>%s</title>
          <style>
            body {
              color: black;
              background-color: #FFFF00;
              text-align: center;
            }
            input {
              background-color: #FF9900;
              border: 2px #FF9900;
              color: blue;
              padding: 3px 6px;
              text-align: center;
              text-decoration: none;
              display: inline-block;
              font-size: 16px;
              cursor: pointer;
              border-radius: 7px;
            }
            input:hover {
              background-color: #FF4400;
            }
          </style>
        </head>
        <body>
        <h1 style='color:red;'>%s</H1>
      )=====", hTitle, hTitle);
}


// ----------------------------------------------------------------
//      send a standard html footer (i.e. end of web page)
// ----------------------------------------------------------------
void sendFooter(WiFiClient &client) {
  client.write("</body></html>\n");
  delay(3);
  client.stop();
}


// ----------------------------------------------------------------
//  send line of text to both serial port and web page - used by readRGBImage
// ----------------------------------------------------------------
void sendText(WiFiClient &client, String theText) {
     if (!sendRGBfile) client.print(theText + "<br>\n");
     if (serialDebug || theText.indexOf("error") > 0) Serial.println(theText);   // if text contains "error"
}


// ----------------------------------------------------------------
//                        reset the camera
// ----------------------------------------------------------------
// either hardware(1) or software(0)
void resetCamera(bool type = 0) {
  if (type == 1) {
    // power cycle the camera module (handy if camera stops responding)
      digitalWrite(PWDN_GPIO_NUM, HIGH);    // turn power off to camera module
      delay(300);
      digitalWrite(PWDN_GPIO_NUM, LOW);
      delay(300);
      initialiseCamera();
    } else {
    // reset via software (handy if you wish to change resolution or image type etc. - see test procedure)
      esp_camera_deinit();
      delay(50);
      initialiseCamera();
    }
}


// ----------------------------------------------------------------
//                    -change image resolution
// ----------------------------------------------------------------
// if required resolution not supplied it cycles through several
// note: this stops PWM on the flash working for some reason
void changeResolution(framesize_t tRes = FRAMESIZE_96X96) {
  esp_camera_deinit();     // disable camera
  delay(50);
  if (tRes == FRAMESIZE_96X96) {      // taken as none supplied so cycle through several
    if (FRAME_SIZE_IMAGE == FRAMESIZE_QVGA) tRes = FRAMESIZE_VGA;
    else if (FRAME_SIZE_IMAGE == FRAMESIZE_VGA) tRes = FRAMESIZE_XGA;
    else if (FRAME_SIZE_IMAGE == FRAMESIZE_XGA) tRes = FRAMESIZE_UXGA;
    else tRes = FRAMESIZE_QVGA;
  }
  FRAME_SIZE_IMAGE = tRes;
  initialiseCamera();
  if (serialDebug) Serial.println("Camera resolution changed to " + String(tRes));
  ImageResDetails = "Unknown";   // set next time image captured
}


// ******************************************************************************************************************


// ----------------------------------------------------------------
//     Capture image from camera and save to spiffs or sd card
// ----------------------------------------------------------------
// returns 0 if failed, 1 if stored in spiffs, 2 if stored on sd card

byte storeImage() {

 byte sRes = 0;                // result flag
 fs::FS &fs = SD_MMC;          // sd card file system

 // capture the image from camera
   int currentBrightness = brightLEDbrightness;
   if (flashRequired) brightLed(255);   // change LED brightness (0 - 255)
   camera_fb_t *fb = esp_camera_fb_get();             // capture image frame from camera
   if (flashRequired) brightLed(currentBrightness);   // change LED brightness back to previous state
   if (!fb) {
     if (serialDebug) Serial.println("Error: Camera capture failed");
     return 0;
   }

 // save image to Spiffs
   if (!sdcardPresent) {
     if (serialDebug) Serial.println("Storing image to spiffs only");
     SPIFFS.remove(spiffsFilename);                         // if file name already exists delete it
     File file = SPIFFS.open(spiffsFilename, FILE_WRITE);   // create new file
     if (!file) {
       if (serialDebug) Serial.println("Failed to create file in Spiffs - will format and try again");
       if (!SPIFFS.format()) {                              // format spiffs
         if (serialDebug) Serial.println("Spiffs format failed");
       } else {
         file = SPIFFS.open(spiffsFilename, FILE_WRITE);    // try again to create new file
         if (!file) {
           if (serialDebug) Serial.println("Still unable to create file in spiffs");
         }
       }
     }
     if (file) {       // if file has been created ok write image data to it
       if (file.write(fb->buf, fb->len)) {
         sRes = 1;    // flag as saved ok
       } else {
         if (serialDebug) Serial.println("Error: failed to write image data to spiffs file");
       }
     }
     if (sRes == 1 && serialDebug) {
       Serial.print("The picture has been saved to Spiffs as " + spiffsFilename);
       Serial.print(" - Size: ");
       Serial.print(file.size());
       Serial.println(" bytes");
     }
     file.close();
   }


 // save the image to sd card
   if (sdcardPresent) {
     if (serialDebug) Serial.printf("Storing image #%d to sd card \n", imageCounter);
     String SDfilename = "/img/" + String(imageCounter + 1) + ".jpg";              // build the image file name
     File file = fs.open(SDfilename, FILE_WRITE);                                  // create file on sd card
     if (!file) {
       if (serialDebug) Serial.println("Error: Failed to create file on sd-card: " + SDfilename);
     } else {
       if (file.write(fb->buf, fb->len)) {                                         // File created ok so save image to it
         if (serialDebug) Serial.println("Image saved to sd card");
         imageCounter ++;                                                          // increment image counter
         sRes = 2;    // flag as saved ok
       } else {
         if (serialDebug) Serial.println("Error: failed to save image data file on sd card");
       }
       file.close();              // close image file on sd card
     }
   }

 esp_camera_fb_return(fb);        // return frame so memory can be released

 return sRes;

} // storeImage


// ******************************************************************************************************************


// ----------------------------------------------------------------
//            -Action any user input on root web page
// ----------------------------------------------------------------

void rootUserInput(WiFiClient &client) {

    // if button1 was pressed (toggle io pin A)
    //        Note:  if using an input box etc. you would read the value with the command:    String Bvalue = server.arg("demobutton1");

    // if button1 was pressed (toggle io pin B)
      if (server.hasArg("button1")) {
        if (serialDebug) Serial.println("Button 1 pressed");
        digitalWrite(iopinB,!digitalRead(iopinB));             // toggle output pin on/off
      }

    // if button2 was pressed (Cycle illumination LED)
      if (server.hasArg("button2")) {
        if (serialDebug) Serial.println("Button 2 pressed");
        if (brightLEDbrightness == 0) brightLed(10);                // turn led on dim
        else if (brightLEDbrightness == 10) brightLed(40);          // turn led on medium
        else if (brightLEDbrightness == 40) brightLed(255);         // turn led on full
        else brightLed(0);                                          // turn led off
      }

    // if button3 was pressed (toggle flash)
      if (server.hasArg("button3")) {
        if (serialDebug) Serial.println("Button 3 pressed");
        flashRequired = !flashRequired;
      }

    // if button3 was pressed (format SPIFFS)
      if (server.hasArg("button4")) {
        if (serialDebug) Serial.println("Button 4 pressed");
        if (!SPIFFS.format()) {
          if (serialDebug) Serial.println("Error: Unable to format Spiffs");
        } else {
          if (serialDebug) Serial.println("Spiffs memory has been formatted");
        }
      }

    // if button4 was pressed (change resolution)
      if (server.hasArg("button5")) {
        if (serialDebug) Serial.println("Button 5 pressed");
        changeResolution();   // cycle through some options
      }

    // if brightness was adjusted - cameraImageBrightness
        if (server.hasArg("bright")) {
          String Tvalue = server.arg("bright");   // read value
          if (Tvalue != NULL) {
            int val = Tvalue.toInt();
            if (val >= -2 && val <= 2 && val != cameraImageBrightness) {
              if (serialDebug) Serial.printf("Brightness changed to %d\n", val);
              cameraImageBrightness = val;
              cameraImageSettings();           // Apply camera image settings
            }
          }
        }

    // if exposure was adjusted - cameraImageExposure
        if (server.hasArg("exp")) {
          if (serialDebug) Serial.println("Exposure has been changed");
          String Tvalue = server.arg("exp");   // read value
          if (Tvalue != NULL) {
            int val = Tvalue.toInt();
            if (val >= 0 && val <= 1200 && val != cameraImageExposure) {
              if (serialDebug) Serial.printf("Exposure changed to %d\n", val);
              cameraImageExposure = val;
              cameraImageSettings();           // Apply camera image settings
            }
          }
        }

     // if image gain was adjusted - cameraImageGain
        if (server.hasArg("gain")) {
          if (serialDebug) Serial.println("Gain has been changed");
          String Tvalue = server.arg("gain");   // read value
            if (Tvalue != NULL) {
              int val = Tvalue.toInt();
              if (val >= 0 && val <= 31 && val != cameraImageGain) {
                if (serialDebug) Serial.printf("Gain changed to %d\n", val);
                cameraImageGain = val;
                cameraImageSettings();          // Apply camera image settings
              }
            }
         }
  }


// ----------------------------------------------------------------
//       -root web page requested    i.e. http://x.x.x.x/
// ----------------------------------------------------------------
// web page with control buttons, links etc.

void handleRoot() {

 getNTPtime(2);                                             // refresh current time from NTP server
 WiFiClient client = server.client();                       // open link with client

 rootUserInput(client);                                     // Action any user input from this web page

 // html header
   sendHeader(client, "ESP32Cam demo sketch");
   client.write("<FORM action='/' method='post'>\n");            // used by the buttons in the html (action = the web page to send it to


 // --------------------------------------------------------------------

 // html main body
 //                    Info on the arduino ethernet library:  https://www.arduino.cc/en/Reference/Ethernet
 //                                            Info in HTML:  https://www.w3schools.com/html/
 //     Info on Javascript (can be inserted in to the HTML):  https://www.w3schools.com/js/default.asp
 //                               Verify your HTML is valid:  https://validator.w3.org/


  client.write("Sketch from: github.com/alanesq/esp32cam-demo<br>");

  // ---------------------------------------------------------------------------------------------
  //  info which is periodically updated usin AJAX - https://www.w3schools.com/xml/ajax_intro.asp

    // sd card
      if (!sdcardPresent) {
        client.println("<p style='color:blue;'>NO SD CARD DETECTED<span id=uImages></span><span id=uUsed></span><span id=uRemain></span></p>");    // spans will be left empty
      } else {
        client.println("<p>SD Card: <span id=uImages> - </span> images stored, <span id=uUsed> - </span>MB used , <span id=uRemain> - </span>MB remaining</p>\n");
      }

    // illumination/flash led
      client.println("Illumination led brightness=<span id=uBrightness> - </span>, Flash is <span id=uFlash> - </span>");

    // Current real time
      client.println("<br>Current time: <span id=uTime> - </span>");

    // gpio pin status
      client.print("<br>GPIO output pin 12 is: <span id=uGPIO12> - </span>, GPIO input pin 13 is: <span id=uGPIO13> - </span>");

    // image resolution
      client.println("<br>Image size: <span id=uRes> - </span>");

    // Javascript - to periodically update above getting info from http://x.x.x.x/data
      client.printf(R"=====(
         <script>
            function getData() {
              var xhttp = new XMLHttpRequest();
              xhttp.onreadystatechange = function() {
              if (this.readyState == 4 && this.status == 200) {
                var receivedArr = this.responseText.split(',');
                document.getElementById('uImages').innerHTML = receivedArr[0];
                document.getElementById('uUsed').innerHTML = receivedArr[1];
                document.getElementById('uRemain').innerHTML = receivedArr[2];
                document.getElementById('uBrightness').innerHTML = receivedArr[3];
                document.getElementById('uTime').innerHTML = receivedArr[4];
                document.getElementById('uGPIO12').innerHTML = receivedArr[5];
                document.getElementById('uGPIO13').innerHTML = receivedArr[6];
                document.getElementById('uFlash').innerHTML = receivedArr[7];
                document.getElementById('uRes').innerHTML = receivedArr[8];
              }
            };
            xhttp.open('GET', 'data', true);
            xhttp.send();}
            getData();
            setInterval(function() { getData(); }, %d);
         </script>
      )=====", datarefresh);

  // ---------------------------------------------------------------------------------------------


//    // touch input on the two gpio pins
//      client.printf("<p>Touch on pin 12: %d </p>\n", touchRead(T5) );
//      client.printf("<p>Touch on pin 13: %d </p>\n", touchRead(T4) );

   // Control buttons
     client.write("<br><br>");
     client.write("<input style='height: 35px;' name='button1' value='Toggle pin 12' type='submit'> \n");
     client.write("<input style='height: 35px;' name='button2' value='Cycle illumination LED' type='submit'> \n");
     client.write("<input style='height: 35px;' name='button3' value='Toggle Flash' type='submit'> \n");
     client.write("<input style='height: 35px;' name='button4' value='Wipe SPIFFS memory' type='submit'> \n");
     client.write("<input style='height: 35px;' name='button5' value='Change Resolution' type='submit'><br> \n");

   // Image setting controls
     client.println("<br>CAMERA SETTINGS: ");
     client.printf("Brightness: <input type='number' style='width: 50px' name='bright' title='from -2 to +2' min='-2' max='2' value='%d'>  \n", cameraImageBrightness);
     client.printf("Exposure: <input type='number' style='width: 50px' name='exp' title='from 0 to 1200' min='0' max='1200' value='%d'>  \n", cameraImageExposure);
     client.printf("Gain: <input type='number' style='width: 50px' name='gain' title='from 0 to 30' min='0' max='30' value='%d'>\n", cameraImageGain);
     client.println(" <input type='submit' name='submit' value='Submit change / Refresh Image'>");
     client.println("<br>Set exposure and gain to zero for auto adjust");

   // links to the other pages available
     client.write("<br><br>LINKS: \n");
     client.write("<a href='/photo'>Capture an image</a> - \n");
     client.write("<a href='/img'>View stored image</a> - \n");
     client.write("<a href='/rgb'>Capture Image as raw RGB data</a> - \n");
     client.write("<a href='/stream'>Live stream</a> - \n");
     client.write("<a href='/test'>Test procedure</a><br>\n");

    // capture and show a jpg image
      client.write("<br><a href='/jpg'>");         // make it a link
      client.write("<img src='/jpg' /> </a>");     // show image from http://x.x.x.x/jpg

    // javascript to refresh the image periodically
      client.printf(R"=====(
         <script>
           function refreshImage(){
               var timestamp = new Date().getTime();
               var el = document.getElementById('image1');
               var queryString = '?t=' + timestamp;
               el.src = '/jpg' + queryString;
           }
           setInterval(function() { refreshImage(); }, %d);
         </script>
      )=====", imagerefresh);


 // --------------------------------------------------------------------


 sendFooter(client);     // close web page

}  // handleRoot


// ----------------------------------------------------------------
//     -data web page requested     i.e. http://x.x.x.x/data
// ----------------------------------------------------------------
// suplies changing info to update root web page as comma seperated String

void handleData(){

  // sd sdcard
    uint32_t SDusedSpace = 0;
    uint32_t SDtotalSpace = 0;
    uint32_t SDfreeSpace = 0;
    if (sdcardPresent) {
      SDusedSpace = SD_MMC.usedBytes() / (1024 * 1024);
      SDtotalSpace = SD_MMC.totalBytes() / (1024 * 1024);
      SDfreeSpace = SDtotalSpace - SDusedSpace;
    }

   String reply = "";
    if (sdcardPresent) reply += String(imageCounter);      // images stored
    reply += ",";
    if (sdcardPresent) reply += String(SDusedSpace);       // space used on sd card
    reply += ",";
    if (sdcardPresent) reply += String(SDfreeSpace);       // space remaining on sd card
    reply += ",";
    reply += String(brightLEDbrightness);   // illumination led brightness
    reply += ",";
    reply += localTime();               // current date/time
    reply += ",";
    reply += (digitalRead(iopinB)==1) ? "ON" : "OFF";       // output gpio pin status
    reply += ",";
    reply += (digitalRead(iopinA)==1) ? "ON" : "OFF";       // output gpio pin status
    reply += ",";
    reply += (flashRequired==1) ? "ON" : "OFF";       // if flash is enabled
    reply += ",";
    reply += ImageResDetails;          // if flash is enabled
    //reply += ",";

   server.send(200, "text/plane", reply); //Send millis value only to client ajax request
}


// ******************************************************************************************************************


// ----------------------------------------------------------------
//    -photo save to sd card/spiffs    i.e. http://x.x.x.x/photo
// ----------------------------------------------------------------
// web page to capture an image from camera and save to spiffs or sd card

void handlePhoto() {

 WiFiClient client = server.client();                                                        // open link with client

 // log page request including clients IP
   IPAddress cIP = client.remoteIP();
   if (serialDebug) Serial.println("Save photo requested by " + cIP.toString());

 byte sRes = storeImage();   // capture and save an image to sd card or spiffs (store sucess or failed flag - 0=fail, 1=spiffs only, 2=spiffs and sd card)

 // html header
   sendHeader(client, "Capture and save image");

 // html body
   if (sRes == 2) {
       client.printf("<p>Image saved to sd card as image number %d </p>\n", imageCounter);
   } else if (sRes == 1) {
       client.write("<p>Image saved in Spiffs</p>\n");
   } else {
       client.write("<p>Error: Failed to save image</p>\n");
   }

   client.write("<a href='/'>Return</a>\n");       // link back

 // close web page
   sendFooter(client);

}  // handlePhoto



// ----------------------------------------------------------------
// -display image stored on sd card or SPIFFS   i.e. http://x.x.x.x/img?img=x
// ----------------------------------------------------------------
// Display a previously stored image, default image = most recent
// returns 1 if image displayed ok

bool handleImg() {

   WiFiClient client = server.client();                 // open link with client
   bool pRes = 0;

   // log page request including clients IP
     IPAddress cIP = client.remoteIP();
     if (serialDebug) Serial.println("Display stored image requested by " + cIP.toString());

   int imgToShow = imageCounter;                        // default to showing most recent file

   // get image number from url parameter
     if (server.hasArg("img") && sdcardPresent) {
       String Tvalue = server.arg("img");               // read value
       imgToShow = Tvalue.toInt();                      // convert string to int
       if (imgToShow < 1 || imgToShow > imageCounter) imgToShow = imageCounter;    // validate image number
     }

   // if stored on sd card
   if (sdcardPresent) {
     if (serialDebug) Serial.printf("Displaying image #%d from sd card", imgToShow);

     String tFileName = "/img/" + String(imgToShow) + ".jpg";
     fs::FS &fs = SD_MMC;                                 // sd card file system
     File timg = fs.open(tFileName, "r");
     if (timg) {
         size_t sent = server.streamFile(timg, "image/jpeg");     // send the image
         timg.close();
         pRes = 1;                                                // flag sucess
     } else {
       if (serialDebug) Serial.println("Error: image file not found");
       sendHeader(client, "Display stored image");
       client.write("<p>Error: Image not found</p></html>\n");
       client.write("<br><a href='/'>Return</a>\n");       // link back
       sendFooter(client);     // close web page
     }
   }

   // if stored in SPIFFS
   if (!sdcardPresent) {
     if (serialDebug) Serial.println("Displaying image from spiffs");

     // check file exists
     if (!SPIFFS.exists(spiffsFilename)) {
       sendHeader(client, "Display stored image");
       client.write("Error: No image found to display\n");
       client.write("<br><a href='/'>Return</a>\n");       // link back
       sendFooter(client);     // close web page
       return 0;
     }

     File f = SPIFFS.open(spiffsFilename, "r");                         // read file from spiffs
         if (!f) {
           if (serialDebug) Serial.println("Error reading " + spiffsFilename);
           sendHeader(client, "Display stored image");
           client.write("Error reading file from Spiffs\n");
           client.write("<br><a href='/'>Return</a>\n");       // link back
           sendFooter(client);     // close web page
         }
         else {
             size_t sent = server.streamFile(f, "image/jpeg");     // send file to web page
             if (!sent) {
               if (serialDebug) Serial.println("Error sending " + spiffsFilename);
             } else {
               pRes = 1;                                           // flag sucess
             }
             f.close();
         }
   }
   return pRes;

}  // handleImg


// ******************************************************************************************************************


// ----------------------------------------------------------------
//                      -invalid web page requested
// ----------------------------------------------------------------
// Note: shows a different way to send the HTML reply

void handleNotFound() {

 String tReply;

 if (serialDebug) Serial.print("Invalid page requested");

 tReply = "File Not Found\n\n";
 tReply += "URI: ";
 tReply += server.uri();
 tReply += "\nMethod: ";
 tReply += ( server.method() == HTTP_GET ) ? "GET" : "POST";
 tReply += "\nArguments: ";
 tReply += server.args();
 tReply += "\n";

 for ( uint8_t i = 0; i < server.args(); i++ ) {
   tReply += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
 }

 server.send ( 404, "text/plain", tReply );
 tReply = "";      // clear variable

}  // handleNotFound


// ******************************************************************************************************************


// ----------------------------------------------------------------
//      -access image data as RGB - i.e. http://x.x.x.x/rgb
// ----------------------------------------------------------------
//Demonstration on how to access raw RGB data from the camera
// Notes:
//     Set sendRGBfile to 1 in the settings at top of sketch to just send the raw rgb data as a file which can then be used with
//       the Processing sketch: https://github.com/alanesq/esp32cam-demo/blob/master/Misc/displayRGB.pde
//       otherwise a web page is displayed showing some sample rgb data usage.
//     You may want to disable auto white balance when experimenting with RGB otherwise the camera is always trying to adjust the
//        image colours to mainly white.   (disable in the 'cameraImageSettings' procedure).
//     It will fail on the higher resolutions as it requires more than the 4mb of available psram to store the data (1600x1200x3 bytes)
//     I learned how to read the RGB data from: https://github.com/Makerfabs/Project_Touch-Screen-Camera/blob/master/Camera_v2/Camera_v2.ino

void readRGBImage() {
                                                                                            // used for timing operations
 WiFiClient client = server.client();
 uint32_t tTimer;     // used to time tasks                                                                    // open link with client

 if (!sendRGBfile) {
   // html header
    sendHeader(client, "Show RGB data");

   // page title including clients IP
     IPAddress cIP = client.remoteIP();
     sendText(client, "Live image as rgb data, requested by " + cIP.toString());                                                            // 'sendText' sends the String to both serial port and web page
 }

 // make sure psram is available
 if (!psramFound()) {
   sendText(client,"error: no psram available to store the RGB data");
   client.write("<br><a href='/'>Return</a>\n");       // link back
   if (!sendRGBfile) sendFooter(client);               // close web page
   return;
 }


 //   ****** the main code for converting an image to RGB data *****

   // capture a live image from camera (as a jpg)
     camera_fb_t * fb = NULL;
     tTimer = millis();                                                                                    // store time that image capture started
     fb = esp_camera_fb_get();
     if (!fb) {
       sendText(client,"error: failed to capture image from camera");
       client.write("<br><a href='/'>Return</a>\n");       // link back
       if (!sendRGBfile) sendFooter(client);               // close web page
       return;
     } else {
       sendText(client, "-JPG image capture took " + String(millis() - tTimer) + " milliseconds");              // report time it took to capture an image
       sendText(client, "-Image resolution=" + String(fb->width) + "x" + String(fb->height));
       sendText(client, "-Image size=" + String(fb->len) + " bytes");
       sendText(client, "-Image format=" + String(fb->format));
       sendText(client, "-Free memory=" + String(ESP.getFreeHeap()) + " bytes");
     }

/*
   // display captured image using base64 - seems a bit unreliable especially with larger images?
    if (!sendRGBfile) {
      client.print("<br>Displaying image direct from frame buffer");
      String base64data = base64::encode(fb->buf, fb->len);      // convert buffer to base64
      client.print(" - Base64 data length = " + String(base64data.length()) + " bytes\n" );
      client.print("<br><img src='data:image/jpg;base64," + base64data + "'></img><br>\n");
    }
*/

   // allocate memory to store the rgb data (in psram, 3 bytes per pixel)
     sendText(client,"<br>Free psram before rgb data allocated = " + String(heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024) + "K");
     void *ptrVal = NULL;                                                                                 // create a pointer for memory location to store the data
     uint32_t ARRAY_LENGTH = fb->width * fb->height * 3;                                                  // calculate memory required to store the RGB data (i.e. number of pixels in the jpg image x 3)
     if (heap_caps_get_free_size( MALLOC_CAP_SPIRAM) <  ARRAY_LENGTH) {
       sendText(client,"error: not enough free psram to store the rgb data");
       if (!sendRGBfile) {
         client.write("<br><a href='/'>Return</a>\n");    // link back
         sendFooter(client);                              // close web page
       }
       return;
     }
     ptrVal = heap_caps_malloc(ARRAY_LENGTH, MALLOC_CAP_SPIRAM);                                          // allocate memory space for the rgb data
     uint8_t *rgb = (uint8_t *)ptrVal;                                                                    // create the 'rgb' array pointer to the allocated memory space
     sendText(client,"Free psram after rgb data allocated = " + String(heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024) + "K");

   // convert the captured jpg image (fb) to rgb data (store in 'rgb' array)
     tTimer = millis();                                                                                   // store time that image conversion process started
     bool jpeg_converted = fmt2rgb888(fb->buf, fb->len, PIXFORMAT_JPEG, rgb);
     if (!jpeg_converted) {
       sendText(client,"error: failed to convert image to RGB data");
       if (!sendRGBfile) {
         client.write("<br><a href='/'>Return</a>\n");    // link back
         sendFooter(client);                              // close web page
       }
       return;
     }
     sendText(client, "Conversion from jpg to RGB took " + String(millis() - tTimer) + " milliseconds");// report how long the conversion took


   // if sendRGBfile is set then just send raw RGB data and close
   if (sendRGBfile) {
     client.write(rgb, ARRAY_LENGTH);          // send the raw rgb data
     esp_camera_fb_return(fb);                 // camera frame buffer
     delay(3);
     client.stop();
     return;
   }

 //   ****** examples of using the resulting RGB data *****
tft.initR(INITR_BLACKTAB);  
 tft.setRotation(1);
 //tft.fillScreen(ST77XX_BLACK);
 tft.drawPixel(tft.width()/2, tft.height()/2,tft.color565(0,255,0));
 
   // display some of the resulting data
       uint32_t resultsToShow = 57600;                                                                       // how much data to display
     //for (int xx=0;xx<161;++xx){
     
    // }
      sendText(client,"<br>R,G,B data for first " + String(resultsToShow / 3) + " pixels of image");
      for (uint32_t i = 0; i < resultsToShow-2; i+=3) {
       
       //  sendText(client,String(rgb[i+2]) + "," + String(rgb[i+1]) + "," + String(rgb[i+0]));           // Red , Green , Blue     
       //    calculate the x and y coordinate of the current pixel
            uint16_t x = (i / 3) % fb->width;
            uint16_t y = floor( (i / 3) / fb->width);
        tft.drawPixel(x,y,tft.color565(rgb[i+2],rgb[i+1],rgb[i+0]));
       //tft.drawPixel(x,y,tft.color565(x,y,0));
       }


   // find the average values for each colour over entire image
       uint32_t aRed = 0;
       uint32_t aGreen = 0;
       uint32_t aBlue = 0;
       for (uint32_t i = 0; i < (ARRAY_LENGTH - 2); i+=3) {                                               // go through all data and add up totals
         aBlue+=rgb[i];
         aGreen+=rgb[i+1];
         aRed+=rgb[i+2];
       }
       aRed = aRed / (fb->width * fb->height);                                                            // divide total by number of pixels to give the average value
       aGreen = aGreen / (fb->width * fb->height);
       aBlue = aBlue / (fb->width * fb->height);
       sendText(client,"Average Blue = " + String(aBlue));
       sendText(client,"Average Green = " + String(aGreen));
       sendText(client,"Average Red = " + String(aRed));


 //   *******************************************************

 client.write("<br><a href='/'>Return</a>\n");       // link back
 sendFooter(client);     // close web page

 // finished with the data so free up the memory space used in psram
   esp_camera_fb_return(fb);   // camera frame buffer
   heap_caps_free(ptrVal);     // rgb data

}  // readRGBImage



// ******************************************************************************************************************


// ----------------------------------------------------------------
//                      -get time from ntp server
// ----------------------------------------------------------------

bool getNTPtime(int sec) {

   uint32_t start = millis();      // timeout timer

   do {
     time(&now);
     localtime_r(&now, &timeinfo);
     if (serialDebug) Serial.print(".");
     delay(100);
   } while (((millis() - start) <= (1000 * sec)) && (timeinfo.tm_year < (2016 - 1900)));

   if (timeinfo.tm_year <= (2016 - 1900)) return false;  // the NTP call was not successful
   if (serialDebug) {
     Serial.print("now ");
     Serial.println(now);
   }

   // Display time
   if (serialDebug)  {
     char time_output[30];
     strftime(time_output, 30, "%a  %d-%m-%y %T", localtime(&now));
     Serial.println(time_output);
     Serial.println();
   }
 return true;
}


// ----------------------------------------------------------------
//     -capture jpg image and send    i.e. http://x.x.x.x/jpg
// ----------------------------------------------------------------

bool handleJPG() {

    WiFiClient client = server.client();          // open link with client
    char buf[32];

    // capture the jpg image from camera
        camera_fb_t * fb = esp_camera_fb_get();
        if (!fb) {
          if (serialDebug) Serial.println("Error: failed to capture image");
          return 0;
        }

    // store image resolution info.
      ImageResDetails = String(fb->width) + "x" + String(fb->height);

    // html to send a jpg
      const char HEADER[] = "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\n";
      const char CTNTTYPE[] = "Content-Type: image/jpeg\r\nContent-Length: ";
      const int hdrLen = strlen(HEADER);
      const int cntLen = strlen(CTNTTYPE);
      client.write(HEADER, hdrLen);
      client.write(CTNTTYPE, cntLen);
      sprintf( buf, "%d\r\n\r\n", fb->len);      // put text size in to 'buf' char array and send
      client.write(buf, strlen(buf));

    // send the captured jpg data
      client.write((char *)fb->buf, fb->len);

    // close client connection
      delay(3);
      client.stop();

    // return image frame so memory can be released
      esp_camera_fb_return(fb);

    return 1;

}  // handleJPG



// ----------------------------------------------------------------
//      -stream requested     i.e. http://x.x.x.x/stream
// ----------------------------------------------------------------
// Sends video stream - thanks to Uwe Gerlach for the code showing me how to do this

void handleStream(){

  WiFiClient client = server.client();          // open link with client
  char buf[32];
  camera_fb_t * fb = NULL;

  // log page request including clients IP
    IPAddress cIP = client.remoteIP();
    if (serialDebug) Serial.println("Live stream requested by " + cIP.toString());

 // html
 const char HEADER[] = "HTTP/1.1 200 OK\r\n" \
                       "Access-Control-Allow-Origin: *\r\n" \
                       "Content-Type: multipart/x-mixed-replace; boundary=123456789000000000000987654321\r\n";
 const char BOUNDARY[] = "\r\n--123456789000000000000987654321\r\n";           // marks end of each image frame
 const char CTNTTYPE[] = "Content-Type: image/jpeg\r\nContent-Length: ";       // marks start of image data
 const int hdrLen = strlen(HEADER);         // length of the stored text, used when sending to web page
 const int bdrLen = strlen(BOUNDARY);
 const int cntLen = strlen(CTNTTYPE);
 client.write(HEADER, hdrLen);
 client.write(BOUNDARY, bdrLen);

 // send live jpg images until client disconnects
 while (true)
 {
   if (!client.connected()) break;
     fb = esp_camera_fb_get();                   // capture live image as jpg
     if (!fb) {
       if (serialDebug) Serial.println("Error: failed to capture jpg image");
     } else {
      // send image
       client.write(CTNTTYPE, cntLen);             // send content type html (i.e. jpg image)
       sprintf( buf, "%d\r\n\r\n", fb->len);       // format the image's size as html and put in to 'buf'
       client.write(buf, strlen(buf));             // send result (image size)
       client.write((char *)fb->buf, fb->len);     // send the image data
       client.write(BOUNDARY, bdrLen);             // send html boundary      see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
       esp_camera_fb_return(fb);                   // return image buffer so memory can be released
   }
 }

 if (serialDebug) Serial.println("Video stream stopped");
 delay(3);
 client.stop();


}  // handleStream


// ******************************************************************************************************************


// ----------------------------------------------------------------
//                        request a web page
// ----------------------------------------------------------------
//   @param    page         web page to request
//   @param    received     String to store response in
//   @param    maxWaitTime  maximum time to wait for reply (ms)
//   @returns  http code
// see:  https://randomnerdtutorials.com/esp32-http-get-post-arduino/#http-get-1
// to do:  limit size of reply

int requestWebPage(String* page, String* received, int maxWaitTime=5000){

  if (serialDebug) Serial.println("requesting web page: " + *page);

  WiFiClient client;
  HTTPClient http;     // see:  https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266HTTPClient
  http.setTimeout(maxWaitTime);
  http.begin(client, *page);      // for https requires (client, *page, thumbprint)  e.g.String thumbprint="08:3B:71:72:02:43:6E:CA:ED:42:86:93:BA:7E:DF:81:C4:BC:62:30";
  int httpCode = http.GET();      // http codes: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
  if (serialDebug) Serial.println("http code: " + String(httpCode));

  if (httpCode > 0) {
    *received = http.getString();
  } else {
    *received = "error:" + String(httpCode);
  }
  if (serialDebug) Serial.println(*received);

  http.end();   //Close connection
  if (serialDebug) Serial.println("Web connection closed");

  return httpCode;

}  // requestWebPage


// ******************************************************************************************************************


// ----------------------------------------------------------------
//       -test procedure    i.e. http://x.x.x.x/test
// ----------------------------------------------------------------

void handleTest() {

 WiFiClient client = server.client();                                                        // open link with client

 // log page request including clients IP
   IPAddress cIP = client.remoteIP();
   if (serialDebug) Serial.println("Test page requested by " + cIP.toString());

 // html header
   sendHeader(client, "Testing");


 // html body


 // -------------------------------------------------------------------




                          // test code goes here




// demo of drawing on the camera image using javascript / html canvas
//   could be of use to show area of interest on the image etc. - see https://www.w3schools.com/html/html5_canvas.asp
// creat a DIV and put image in it with a html canvas on top of it
  int imageWidth = 640;   // image dimensions on web page
  int imageHeight = 480;
  client.println("<div style='display:inline-block;position:relative;'>");
  client.println("<img style='position:absolute;z-index:10;' src='/jpg' width='" + String(imageWidth) + "' height='" + String(imageHeight) + "' />");
  client.println("<canvas style='position:relative;z-index:20;' id='myCanvas' width='" + String(imageWidth) + "' height='" + String(imageHeight) + "'></canvas>");
  client.println("</div>");
// javascript to draw on the canvas
  client.println("<script>");
  client.println("var imageWidth = " + String(imageWidth) + ";");
  client.println("var imageHeight = " + String(imageHeight) + ";");
  client.print (R"=====(
    // connect to the canvas
      var c = document.getElementById("myCanvas");
      var ctx = c.getContext("2d");
      ctx.strokeStyle = "red";
    // draw on image
      ctx.rect(imageWidth / 2, imageHeight / 2, 60, 40);                              // box
      ctx.moveTo(20, 20); ctx.lineTo(200, 100);                                       // line
      ctx.font = "30px Arial";  ctx.fillText("Hello World", 50, imageHeight - 50);    // text
      ctx.stroke();
   </script>\n)=====");


/*
 // demo of how to request a web page
   String page = "http://urlhere.com";   // url to request
   String response;                             // reply will be stored here
   int httpCode = requestWebPage(&page, &response);
   // show results
     client.println("Web page requested: '" + page + "' - http code: " + String(httpCode));
     client.print("<xmp>'");     // enables the html code to be displayed
     client.print(response);
     client.println("'</xmp><br>");
*/


/*
//  // demo useage of the mcp23017 io chipnote: this stops PWM on the flash working for some reason
    #if useMCP23017 == 1
      while(1) {
          mcp.digitalWrite(0, HIGH);
          int q = mcp.digitalRead(8);
          client.print("<p>HIGH, input =" + String(q) + "</p>");
          delay(1000);
          mcp.digitalWrite(0, LOW);
          client.print("<p>LOW</p>");
          delay(1000);
      }
    #endif
*/


 // -------------------------------------------------------------------

 client.println("<br><br><a href='/'>Return</a>");       // link back
 sendFooter(client);     // close web page

}  // handleTest


// ******************************************************************************************************************
// end

Не в сети

#8 08-03-2022 23:51:25

IvanAltay
Administrator
Зарегистрирован: 03-05-2018
Сообщений: 4,586

Re: ESP32CAM + tft ST7735S.

Не в сети

Подвал раздела

Работает на FluxBB (перевод Laravel.ru)