#include <WiFi.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <SD.h>
#include <HardwareSerial.h> // For Serial1 and Serial2 (GPS, GSM)
// Define separate SPI objects for SD Card (HSPI) and Camera (VSPI)
SPIClass hspi(HSPI); // For SD Card
SPIClass vspi(VSPI); // For Camera
// --- CAMERA DEFINITIONS (OV5642 - SPI) ---
#define CAMERA_CS_PIN 5 // Connected to ESP32 GPIO5
#define CAMERA_BUFFER_LEN 120 * 240 // Image resolution for simulation
uint8_t cameraBuffer[CAMERA_BUFFER_LEN];
typedef struct BoundingBoxStruct {
int top;
int left;
int width;
int height;
} BoundingBox;
typedef struct BoundingBoxesStruct {
BoundingBox* boxes;
int length;
} BoundingBoxes;
typedef struct LicencePlateStruct {
char* licencePlate;
int parkingSpaceId;
} LicencePlate;
typedef struct LicencePlatesStruct {
LicencePlate* licencePlates;
int length;
} LicencePlates;
// Gets image data from camera chip using SPI and updates the buffer variable
uint8_t* getImageData() {
digitalWrite(CAMERA_CS_PIN, LOW);
vspi.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); // Use VSPI
vspi.transfer(cameraBuffer, CAMERA_BUFFER_LEN);
vspi.endTransaction();
digitalWrite(CAMERA_CS_PIN, HIGH);
Serial.println("Camera: Image data captured.");
return cameraBuffer;
}
BoundingBoxes* getBoundingBoxes(uint8_t* imageData) {
// This would run the YOLOv8 model to detect the licence plate bounding boxes
// Returns set data in simulation
BoundingBox boundingBox = {
.top = 10,
.left = 20,
.width = 35,
.height = 17
};
BoundingBox* boundingBoxArr = (BoundingBox*) malloc(sizeof(BoundingBox));
*boundingBoxArr = boundingBox;
BoundingBoxes* boundingBoxesPtr = (BoundingBoxes*) malloc(sizeof(BoundingBoxes));
BoundingBoxes boundingBoxes = {
.boxes = boundingBoxArr,
.length = 1
};
*boundingBoxesPtr = boundingBoxes;
return boundingBoxesPtr;
}
void getLicencePlateFromBoundingBox(BoundingBox boundingBox, char* output) {
// This would run an OCR model to detect the licence plate text
// Returns set data in simulation
strcpy(output, "GF57XWD");
}
int getParkingSpaceIdFromBoundingBox(BoundingBox boundingBox) {
// This would probably check a map of the parking space
// Then apply a matrix transformation to the bounding box
// Then compare the transformed coords to the map and get the closest space
// In simulation just returns static data
return 1;
}
LicencePlates* getLicencePlatesFromImageData(uint8_t* imageData) {
BoundingBoxes* boundingBoxes = getBoundingBoxes(imageData);
LicencePlate* licencePlateArr = (LicencePlate*) malloc(sizeof(LicencePlate) * boundingBoxes->length);
for (int i = 0; i < boundingBoxes->length; i++) {
BoundingBox boundingBox = boundingBoxes->boxes[i];
char* licencePlateStrPtr = (char*) malloc(sizeof(char) * 8);
getLicencePlateFromBoundingBox(boundingBox, licencePlateStrPtr);
licencePlateArr[i].licencePlate = licencePlateStrPtr;
licencePlateArr[i].parkingSpaceId = getParkingSpaceIdFromBoundingBox(boundingBox);
}
LicencePlates licencePlatesStruct = {
.licencePlates = licencePlateArr,
.length = boundingBoxes->length
};
LicencePlates* licencePlatesStructPtr = (LicencePlates*) malloc(sizeof(LicencePlates));
*licencePlatesStructPtr = licencePlatesStruct;
return licencePlatesStructPtr;
}
void freeBoundingBoxes(BoundingBoxes* boundingBoxes) {
free(boundingBoxes->boxes);
free(boundingBoxes);
}
void freeLicencePlates(LicencePlates* licencePlates) {
for (int i = 0; i < licencePlates->length; i++) {
free(licencePlates->licencePlates[i].licencePlate);
}
free(licencePlates->licencePlates);
free(licencePlates);
}
void sendHttpRequest(String url, String body) {
// Send the HTTPS request to the API
HTTPClient http;
http.begin(url);
http.addHeader("Content-Type", "application/json");
http.addHeader("API-Secret-Key", "8wPNBM5KFp9z7rLFJHL%yxkX$oDRsq2zQJQCS@4abRWuLj3hfr^ec^SVFxc%3uBS$g*Y!UfrgS%sb!xskkVjQcJq#uJ4vvdcigUyR32CzqmdV&j^Bxit#kXGhGjAMc@%");
http.POST(body);
http.end();
Serial.println("HTTP Request sent to: " + url);
Serial.println("Body: " + body);
}
void sentInitialLicencePlates(LicencePlates* licencePlates) {
String body = "[";
for (int i = 0; i < licencePlates->length; i++) {
if (i > 0) body += ",";
body += "{\"licencePlate\": \"" + String(licencePlates->licencePlates[i].licencePlate) + "\", \"parkingSpaceId\": " + String(licencePlates->licencePlates[i].parkingSpaceId) + "}";
}
body += "]";
sendHttpRequest("https://192.168.2.4/car-park/initial", body);
}
LicencePlates* currentLicencePlates = NULL;
void sendCarLeft(LicencePlate* licencePlate) {
String body = "\"" + String(licencePlate->licencePlate) + "\"";
sendHttpRequest("https://192.168.2.4/car-park/left", body);
}
void sendCarEnter(LicencePlate* licencePlate) {
String body = "{\"licencePlate\": \"" + String(licencePlate->licencePlate) + "\", \"parkingSpaceId\": \"" + String(licencePlate->parkingSpaceId) + "\"}";
sendHttpRequest("https://192.168.2.4/car-park/arrived", body);
}
// --- GPS DEFINITIONS ---
// GPS uses Serial2 (RX2=GPIO16, TX2=GPIO17)
#define GPS_RX_PIN 16
#define GPS_TX_PIN 17
#define GPS_BAUD_RATE 9600
// --- GSM DEFINITIONS ---
// GSM uses Serial1 (RX1=GPIO2, TX1=GPIO4)
#define GSM_RX_PIN 2
#define GSM_TX_PIN 4
#define GSM_BAUD_RATE 9600
#define GSM_RST_PIN 32 // New reset pin for GSM
// --- SD CARD DEFINITIONS ---
// SD Card uses HSPI pins as per your diagram
#define SD_SCK_PIN 14
#define SD_MISO_PIN 12
#define SD_MOSI_PIN 15
#define SD_CS_PIN 13
void setup() {
Serial.begin(115200);
Serial.println("Starting ESP32 Multi-Function System...");
// --- WiFi Setup ---
Serial.println("Connecting to WiFi");
WiFi.begin("Wokwi-GUEST", "");
while(WiFi.status() != WL_CONNECTED){
delay(100);
Serial.print(".");
}
Serial.println("\nWiFi Connected!");
Serial.println("IP Address: " + WiFi.localIP().toString());
// --- Camera Setup (VSPI) ---
vspi.begin(18, 19, 23, CAMERA_CS_PIN); // SCK, MISO, MOSI, SS (CS for camera)
pinMode(CAMERA_CS_PIN, OUTPUT);
digitalWrite(CAMERA_CS_PIN, HIGH); // Ensure camera is deselected initially
Serial.println("Camera (OV5642) initialized on VSPI (Pins: SCK-18, MISO-19, MOSI-23, CS-5).");
// --- GPS Setup (Serial2) ---
Serial2.begin(GPS_BAUD_RATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
Serial.println("GPS UART initialized on Serial2 (Pins: RX-16, TX-17).");
// --- GSM Setup (Serial1) ---
pinMode(GSM_RST_PIN, OUTPUT);
digitalWrite(GSM_RST_PIN, HIGH); // Ensure module is not in reset
Serial1.begin(GSM_BAUD_RATE, SERIAL_8N1, GSM_RX_PIN, GSM_TX_PIN); // RX1, TX1
Serial.println("GSM UART initialized on Serial1 (Pins: RX-2, TX-4, RST-32).");
delay(1000); // Give GSM module time to start
Serial1.println("AT"); // Send a basic AT command
Serial.println("GSM: Sent AT command.");
// --- SD Card Setup (HSPI) ---
hspi.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, -1); // SCK, MISO, MOSI, SS (SS is -1 if not used by library)
if(!SD.begin(SD_CS_PIN, hspi)){ // Pass the specific SPI bus (hspi)
Serial.println("SD Card: Initialization failed!");
return;
}
Serial.println("SD Card: Initialization done on HSPI (Pins: SCK-14, MISO-12, MOSI-15, CS-13).");
File file = SD.open("/hello.txt", FILE_WRITE);
if(file){
file.println("Hello from ESP32 multi-function project!");
file.close();
Serial.println("SD Card: Wrote to hello.txt");
} else {
Serial.println("SD Card: Error opening hello.txt");
}
}
void loop() {
// --- GPS Data Reading ---
while (Serial2.available()) {
String gpsData = Serial2.readStringUntil('\n');
if (gpsData.length() > 0) {
Serial.print("GPS: ");
Serial.println(gpsData);
// Here you would parse the GPS data (e.g., using TinyGPS++ library)
}
}
// --- GSM Data Reading and AT Command Example ---
if (Serial1.available()) {
String gsmResponse = Serial1.readStringUntil('\n');
Serial.print("GSM: ");
Serial.println(gsmResponse);
// You would parse GSM responses here
}
// Example: Send a periodic AT command or make a call/send SMS based on logic
// if (millis() % 60000 < 100) { // Every minute
// Serial1.println("AT+CSQ"); // Signal Quality
// }
// --- Camera Functionality ---
Serial.println("\n--- Capturing Image ---");
uint8_t* imageData = getImageData();
Serial.println("Image captured, processing...");
LicencePlates* newLicencePlates = getLicencePlatesFromImageData(imageData);
if (currentLicencePlates == NULL) {
sentInitialLicencePlates(newLicencePlates);
} else {
// Check for cars entering
for (int i = 0; i < newLicencePlates->length; i++) {
bool entered = true;
LicencePlate newPlate = newLicencePlates->licencePlates[i];
for (int j = 0; j < currentLicencePlates->length; j++) {
LicencePlate oldPlate = currentLicencePlates->licencePlates[j];
if (strcmp(newPlate.licencePlate, oldPlate.licencePlate) == 0) {
entered = false;
break;
}
}
if (entered) sendCarEnter(&newPlate);
}
// Check for cars leaving
for (int i = 0; i < currentLicencePlates->length; i++) {
bool left = true;
LicencePlate oldPlate = currentLicencePlates->licencePlates[i];
for (int j = 0; j < newLicencePlates->length; j++) {
LicencePlate newPlate = newLicencePlates->licencePlates[j];
if (strcmp(oldPlate.licencePlate, newPlate.licencePlate) == 0) {
left = false;
break;
}
}
if (left) sendCarLeft(&oldPlate);
}
freeLicencePlates(currentLicencePlates);
}
currentLicencePlates = newLicencePlates;
// --- SD Card Example (Logging) ---
File logFile = SD.open("/log.txt", FILE_APPEND);
if(logFile){
logFile.print(millis());
logFile.print(": Camera processed, ");
logFile.print(currentLicencePlates->length);
logFile.println(" plates detected.");
logFile.close();
} else {
Serial.println("SD Card: Error opening log.txt for append.");
}
delay(5000); // Main loop delay
}