Uploading image to Google Drive (adapting esp32-Cam example)
Posted: Fri Jan 12, 2024 10:58 pm
The aim of my project is to take a screen capture of a TFT and upload it to Google Drive. I am using the FeatherWing 3.5" with onboard SD and Feather Huzzah esp8266. I have all the code worked out to take and make the image and am now trying to adapt an espCam project that uploads, however, I'm running into challenges in doing so
The only examples I have found rely on esp32-Cam library functions that I don't know how to replicate for my environment. Namely these:
Here is the part of the code that I need help on with full code below. This version follows the example https://github.com/gsampallo/esp32cam-g ... gdrive.ino.
This will successfully connect, however the following error message happens (with a long stream of what I assume is byte data )after a bit of chugging:
I think this comes down to what I've tried to do to adapt the esp32-Cam functions that are likely wrong (the commented out being the originals and my solve uncommented):
What I could use is some guidance on how to replicate what is going on in these two functions, as everything else in the examples is not specific to the adapted esp32-Cam example or library.
full code here:
The only examples I have found rely on esp32-Cam library functions that I don't know how to replicate for my environment. Namely these:
Code: Select all
typedef struct {
uint8_t * buf; /*!< Pointer to the pixel data */
size_t len; /*!< Length of the buffer in bytes */
size_t width; /*!< Width of the buffer in pixels */
size_t height; /*!< Height of the buffer in pixels */
pixformat_t format; /*!< Format of the pixel data */
} camera_fb_t;
Code: Select all
void saveCapturedImage(String filename) {
Serial.println("Connect to " + String(host));
client.setInsecure();
if (client.connect(host, port)) {
Serial.println("Client connection successful");
bmpFile = SD.open(filename, FILE_READ);
//char *input = (char *)fb->buf;
char *input = (char *)bmpFile.read();
//int fbLen = fb->len;
int fbLen = sizeof(bmpFile);
char output[base64_enc_len(3)];
String imageFile = "";
for (int i=0; i<fbLen; i++) {
base64_encode(output, (input++), 3);
if (i%3==0) imageFile += urlencode(String(output));
}
String Data = filename+mimeType+myImage;
Serial.println("Send a captured image to Google Drive.");
client.println("POST " + url + " HTTP/1.1");
client.println("Host: " + String(host));
client.println("Content-Length: " + String(Data.length()+imageFile.length()));
client.println("Content-Type: application/x-www-form-urlencoded");
client.println();
client.print(Data);
int Index;
for (Index = 0; Index < imageFile.length(); Index = Index+1000) {
client.print(imageFile.substring(Index, Index+1000));
}
Serial.println("Waiting for response.");
long int StartTime=millis();
while (!client.available()) {
Serial.print(".");
delay(100);
if ((StartTime+waitingTime) < millis()) {
Serial.println();
Serial.println("No response.");
//If you have no response, maybe need a greater value of waitingTime
break;
}
}
Serial.println();
while (client.available()) {
Serial.print(char(client.read()));
}
} else {
Serial.println("Connected to " + String(host) + " failed.");
}
client.stop();
}
Code: Select all
Connect to script.google.com
Client connection successful
Fatal exception 28(LoadProhibitedCause):
epc1=0x4020180b, epc2=0x00000000, epc3=0x00000000, excvaddr=0x00000042, depc=0x00000000
Code: Select all
//char *input = (char *)fb->buf;
char *input = (char *)bmpFile.read();
//int fbLen = fb->len;
int fbLen = sizeof(bmpFile);
full code here:
Code: Select all
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_HX8357.h>
#include <Adafruit_STMPE610.h>
//#include <Adafruit_ImageReader.h>
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
//#include "SdFat.h"
#include "SD.h"
#include <Bounce2.h>
#include "time.h"
#include <Arduino_JSON.h>
#include "Base64.h"
#define STMPE_CS 16
#define TFT_CS 0
#define TFT_DC 15
#define SD_CS 2
#define TFT_RST -1
Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC, TFT_RST);
Adafruit_STMPE610 touch = Adafruit_STMPE610(STMPE_CS);
File bmpFile;
const char* ssid = "mySSID";
const char* password = "myPass";
const char* NTP_SERVER = "pool.ntp.org";
const char* TZ_INFO = "EST5EDT,M3.2.0,M11.1.00";
// const long gmtOffset = -18000; //should be 5 * 3600 or 18000 (seconds)
// const int daylightOffset = 3600; //Replace with your daylight offset (seconds)
tm timeinfo;
time_t now;
long unsigned lastNTPtime;
unsigned long lastEntryTime;
char theDate[20];
char fileDate[20];
//google drive stuff
WiFiClientSecure client;
const char* host = "script.google.com";
const uint16_t port = 443;
String myDeploymentID = "string_of_nonsense";
String url = "/macros/s/string_of_nonsense/exec";
String myMainFolderName = "myFolder";
String mimeType = "&mimetype=image/jpeg";
String myImage = "&data=";
int waitingTime = 30000; //Wait 30 seconds to google response.
// This is calibration data for touchscreen at no rotation
#define TS_MINX 120
#define TS_MAXX 3950
#define TS_MINY 120
#define TS_MAXY 3950
#define PENRADIUS 1
unsigned long lastTouch = millis();
int prevPt[] = {0,0,0};
const int debounceDelay = 50;
#define capPin 17
Bounce captureButton = Bounce();
#define clearPin 5
Bounce clearButton = Bounce();
#define menuPin 4
Bounce menuButton = Bounce();
const unsigned int penColor = 0x0000; //Black
const unsigned int bgColor = 0xF72F; //#F3E779
void setup() {
Serial.begin(115200);
//pinMode(capPin, INPUT);
captureButton.attach(capPin, INPUT);
captureButton.interval(debounceDelay);
//pinMode(clearPin, INPUT);
clearButton.attach(clearPin, INPUT);
clearButton.interval(debounceDelay);
//pinMode(menuPin, INPUT);
menuButton.attach(menuPin, INPUT);
menuButton.interval(debounceDelay);
tft.begin();
//tft.setRotation(1);
tft.fillScreen(bgColor);
if (! touch.begin()) {
Serial.println("STMPE not found!");
while(1);
}
Serial.println("Waiting for touch sense");
Serial.print("Initializing SD card...");
if(!SD.begin(SD_CS)) {
//if(!SD.begin(SD_CS, SD_SCK_MHZ(25))) { // ESP32 requires 25 MHz limit
Serial.println(F("SD begin() failed"));
for(;;); // Fatal error, do not continue
}
Serial.printf("Connecting to %s ", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.println("CONNECTED to WIFI");
configTime(0, 0, NTP_SERVER);
setenv("TZ", TZ_INFO, 1);
if(getNTPtime(5)) { // wait up to 10sec to sync
} else {
Serial.println("Time not set");
//ESP.restart();
}
lastNTPtime = time(&now);
lastEntryTime = millis();
}
void loop() {
captureButton.update();
if(captureButton.fell()){
bmpSave();
}
clearButton.update();
if(clearButton.fell()){
clearScreen();
}
menuButton.update();
if(menuButton.fell()){
menuCall();
}
uint16_t x, y, ptX, ptY;
uint8_t z, ptZ;
if (touch.touched()) {
// read x & y & z;
while (! touch.bufferEmpty()) {
touch.readData(&x, &y, &z);
// normal alignment with no setRotation
ptX = map(x, TS_MINX, TS_MAXX, 0, tft.width());
ptY = map(y, TS_MINY, TS_MAXY, 0, tft.height());
if(millis() - lastTouch < 40){
tft.drawLine(prevPt[0], prevPt[1], ptX, ptY, penColor);
prevPt[0] = ptX; prevPt[1] = ptY; prevPt[2] = ptZ;
lastTouch = millis();
} else{
prevPt[0] = ptX; prevPt[1] = ptY; prevPt[2] = ptZ;
lastTouch = millis();
}
}
}
//delay(10);
}
void drawAngledLine(int x, int y, int x1, int y1) {
float dx = (PENRADIUS+1/2.0) * (x-x1) / sqrt(sq(x-x1) + sq(y-y1));
float dy = (PENRADIUS+1/2.0) * (y-y1) / sqrt(sq(x-x1) + sq(y-y1));
tft.fillTriangle(x+dx, y-dy, x-dx, y+dy, x1+dx, y1-dy, HX8357_WHITE);
tft.fillTriangle(x-dx, y+dy, x1-dx, y1+dy, x1+dx, y1-dy, HX8357_WHITE);
}
void clearScreen() {
tft.fillScreen(bgColor);
}
void bmpSave() {
uint32_t filesize, offset;
uint16_t width = tft.width(), height = tft.height();
unsigned long startTime;
unsigned long elapsedTime;
if(!SD.begin(SD_CS)){
Serial.println(F("SD begin() failed"));
for(;;); // Fatal error, do not continue
}
//String filename = getDate();
//filename = filename + ".bmp";
char filename[20] ;
strcpy(filename, fileDate);
strcat(filename, "_note001.bmp");
while (SD.exists(filename)) {
String num = String(filename).substring(13, 16);
int val = num.toInt();
char stuff[20];
sprintf(stuff, "num in string: %s | value: %d", num, val);
val++;
sprintf(filename, "%s_note%03d.bmp", fileDate, val);
}
Serial.println(filename);
bmpFile = SD.open(filename, FILE_WRITE);
// On error hang up
if (!bmpFile) for (;;);
Serial.print("image: "); Serial.print(filename); Serial.println(" started processing");
//
// File header: 14 bytes
bmpFile.write('B'); bmpFile.write('M');
writeFour(14+40+12+width*height*2); // File size in bytes
writeFour(0);
writeFour(14+40+12); // Offset to image data from start
//
// Image header: 40 bytes
writeFour(40); // Header size
writeFour(width); // Image width
writeFour(height); // Image height
writeTwo(1); // Planes
writeTwo(16); // Bits per pixel
writeFour(0); // Compression (none)
writeFour(0); // Image size (0 for uncompressed)
writeFour(0); // Preferred X resolution (ignore)
writeFour(0); // Preferred Y resolution (ignore)
writeFour(0); // Colour map entries (ignore)
writeFour(0); // Important colours (ignore)
Serial.println("header written");
// Colour masks: 12 bytes
writeFour(0b0000011111100000); // Green
writeFour(0b1111100000000000); // Red
writeFour(0b0000000000011111); // Blue
Serial.println("color mask applied");
// Image data: width * height * 2 bytes
for (int y=height-1; y>=0; y--) {
for (int x=0; x<width; x++) {
writeTwo(getPixel(x,y)); // Each row must be a multiple of four bytes
}
}
Serial.println("finished encoding");
// Close the file
bmpFile.close();
elapsedTime = millis() - startTime;
Serial.println("file finished saving");
Serial.print("the file took "); Serial.print(elapsedTime/1000); Serial.println(" secs to complete");
//uploadFile(filename);
saveCapturedImage(filename);
// http://www.technoblogy.com/show?398X
}
void writeTwo (uint16_t word) {
bmpFile.write(word & 0xFF); bmpFile.write((word >> 8) & 0xFF);
}
void writeFour (uint32_t word) {
bmpFile.write(word & 0xFF); bmpFile.write((word >> 8) & 0xFF);
bmpFile.write((word >> 16) & 0xFF); bmpFile.write((word >> 24) & 0xFF);
}
uint16_t getPixel(int x, int y) { // get pixel color code in rgb565 format
tft.startWrite(); //needed for low-level methods. CS active
tft.setAddrWindow(x, y, 1, 1);
tft.writeCommand(0x2E); // memory read command. sets DC
uint8_t r, g, b;
r = tft.spiRead(); // discard dummy read
r = tft.spiRead();
g = tft.spiRead();
b = tft.spiRead();
tft.endWrite(); //needed for low-level methods. CS idle
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
//https://forum.arduino.cc/t/create-snapshot-of-3-5-tft-and-save-to-file-in-bitmap-format/391367/7
}
bool getNTPtime(int sec) {
{
uint32_t start = millis();
do {
time(&now);
localtime_r(&now, &timeinfo);
Serial.print(".");
delay(10);
} while (((millis() - start) <= (1000 * sec)) && (timeinfo.tm_year < (2016 - 1900)));
if (timeinfo.tm_year <= (2016 - 1900)) return false; // the NTP call was not successful
Serial.print("now "); Serial.println(now);
char time_output[30];
strftime(time_output, 30, "%a %d-%m-%y %T", localtime(&now));
strftime(theDate, 30, "%d-%m-%y", localtime(&now));
strftime(fileDate, 30, "%b%d-%y", localtime(&now));
Serial.println(time_output);
Serial.println();
}
return true;
//https://github.com/SensorsIot/NTP-time-for-ESP8266-and-ESP32/blob/master/NTP_Example/NTP_Example.ino
//https://microcontrollerslab.com/current-date-time-esp8266-nodemcu-ntp-server/
//https://arduino.stackexchange.com/questions/42922/get-hour-with-ctime-time-library-with-esp8266
}
void saveCapturedImage(String filename) {
Serial.println("Connect to " + String(host));
client.setInsecure();
if (client.connect(host, port)) {
Serial.println("Client connection successful");
bmpFile = SD.open(filename, FILE_READ);
//char *input = (char *)fb->buf;
char *input = (char *)bmpFile.read();
//int fbLen = fb->len;
int fbLen = sizeof(bmpFile);
char output[base64_enc_len(3)];
String imageFile = "";
for (int i=0; i<fbLen; i++) {
base64_encode(output, (input++), 3);
if (i%3==0) imageFile += urlencode(String(output));
}
String Data = filename+mimeType+myImage;
Serial.println("Send a captured image to Google Drive.");
client.println("POST " + url + " HTTP/1.1");
client.println("Host: " + String(host));
client.println("Content-Length: " + String(Data.length()+imageFile.length()));
client.println("Content-Type: application/x-www-form-urlencoded");
client.println();
client.print(Data);
int Index;
for (Index = 0; Index < imageFile.length(); Index = Index+1000) {
client.print(imageFile.substring(Index, Index+1000));
}
Serial.println("Waiting for response.");
long int StartTime=millis();
while (!client.available()) {
Serial.print(".");
delay(100);
if ((StartTime+waitingTime) < millis()) {
Serial.println();
Serial.println("No response.");
//If you have no response, maybe need a greater value of waitingTime
break;
}
}
Serial.println();
while (client.available()) {
Serial.print(char(client.read()));
}
} else {
Serial.println("Connected to " + String(host) + " failed.");
}
client.stop();
}
String urlencode(String str) {
String encodedString="";
char c;
char code0;
char code1;
char code2;
for (int i =0; i < str.length(); i++){
c=str.charAt(i);
if (c == ' '){
encodedString+= '+';
} else if (isalnum(c)){
encodedString+=c;
} else{
code1=(c & 0xf)+'0';
if ((c & 0xf) >9){
code1=(c & 0xf) - 10 + 'A';
}
c=(c>>4)&0xf;
code0=c+'0';
if (c > 9){
code0=c - 10 + 'A';
}
code2='\0';
encodedString+='%';
encodedString+=code0;
encodedString+=code1;
//encodedString+=code2;
}
yield();
}
return encodedString;
}