Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 110 additions & 113 deletions cam-module/AdNetCamModule.ino
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
extern "C" {
#include "esp_http_server.h"
}
// Include our helper libraries
#include "FirmwareHash.h"
#include "SecureSign.h"

// ===================
// Select camera model
// ===================
Expand All @@ -15,14 +19,12 @@ extern "C" {
// Enter your WiFi credentials
// ===========================
const char* ssid = "FTTH_2.4ghz";
const char* password = "qefe2fqe";
const char* password = "wefwegefgwe";

// ===========================
// Status LED pin configuration
// ===========================
// Change this to a valid pin for a status LED on your board.
// If you do not have a spare LED, you can disable these lines or
// re-map them. Many ESP32-CAM boards do not have a dedicated status LED.

#define LED_STATUS_PIN 33

// ---------------------------
Expand All @@ -31,8 +33,9 @@ const char* password = "qefe2fqe";
IPAddress local_IP(192, 168, 1, 43);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8); // optional
IPAddress secondaryDNS(8, 8, 4, 4); // optional

IPAddress primaryDNS(8, 8, 8, 8);
IPAddress secondaryDNS(8, 8, 4, 4);

// Forward declarations
static esp_err_t stream_handler(httpd_req_t *req);
Expand All @@ -41,60 +44,66 @@ static void blinkWifiConnecting(void);

httpd_handle_t stream_httpd = NULL;

// Global strings to hold the firmware hash and signature in hex format.
String gFirmwareHashHex = "";
String gSignatureHex = "";

// -----------------------------------------------------------
// Blink the LED rapidly while trying to connect to Wi-Fi
// Blink LED while connecting to Wi-Fi
// -----------------------------------------------------------
static void blinkWifiConnecting(void)
{
static unsigned long lastBlink = 0;
static bool ledState = false;
if (millis() - lastBlink >= 300) { // adjust blink speed if desired
if (millis() - lastBlink >= 300) {
lastBlink = millis();
ledState = !ledState;
digitalWrite(LED_STATUS_PIN, ledState ? HIGH : LOW);
}
}

// -----------------------------------------------------------
// Setup function
// Helper: Convert byte array to hex string
// -----------------------------------------------------------
String bytesToHexString(const uint8_t* data, size_t len) {
String hexStr = "";
for (size_t i = 0; i < len; i++) {
if (data[i] < 16) hexStr += "0";
hexStr += String(data[i], HEX);
}
hexStr.toUpperCase();
return hexStr;
}

// -----------------------------------------------------------
// Setup
// -----------------------------------------------------------
void setup()
{
Serial.begin(115200);
Serial.setDebugOutput(true);
Serial.println();

// Set up the LED (for status) and turn it off initially
pinMode(LED_STATUS_PIN, OUTPUT);
digitalWrite(LED_STATUS_PIN, LOW);

// Static IP configuration
// Configure static IP.
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
Serial.println("STA Failed to configure");
// Turn LED steady on to indicate error
digitalWrite(LED_STATUS_PIN, HIGH);
while (true) {
delay(100);
}
digitalWrite(LED_STATUS_PIN, HIGH); // steady LED = error
while (true) { delay(100); }
}

WiFi.begin(ssid, password);
WiFi.setSleep(false);

Serial.println("WiFi connecting...");

// Blink LED while connecting
while (WiFi.status() != WL_CONNECTED) {
blinkWifiConnecting();
delay(50);
}

// Once connected, turn the LED off (indicating normal operation)
digitalWrite(LED_STATUS_PIN, LOW);

Serial.println("");
Serial.println("WiFi connected. IP address: ");
Serial.println(WiFi.localIP());
Serial.printf("\nWiFi connected. IP address: %s\n", WiFi.localIP().toString().c_str());

// -----------------------------------------------------------
// Camera configuration
Expand All @@ -119,137 +128,129 @@ void setup()
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;

// High-quality capture settings (UXGA + JPEG)
config.frame_size = FRAMESIZE_UXGA; // 1600x1200 (UXGA)
config.pixel_format = PIXFORMAT_JPEG; // for streaming
config.frame_size = FRAMESIZE_UXGA; // 1600x1200 (high quality)
config.pixel_format = PIXFORMAT_JPEG; // for streaming
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.jpeg_quality = 12;
config.fb_count = 1;

// If PSRAM is found, improve performance/quality
if (psramFound()) {
config.jpeg_quality = 10; // better quality
config.fb_count = 2; // double-buffer
config.jpeg_quality = 10;
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
} else {
// If no PSRAM, reduce resolution
config.frame_size = FRAMESIZE_SVGA; // 800x600
config.frame_size = FRAMESIZE_SVGA; // 800x600 if no PSRAM
config.fb_location = CAMERA_FB_IN_DRAM;
}

// -----------------------------------------------------------
// Initialize camera
// -----------------------------------------------------------
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
// Turn LED steady on to indicate camera error
Serial.printf("Camera init failed with error 0x%x\n", err);
digitalWrite(LED_STATUS_PIN, HIGH);
while (true) {
delay(100);
}
while (true) { delay(100); }
}

// Optional sensor adjustments

sensor_t *s = esp_camera_sensor_get();
if (s->id.PID == OV3660_PID) {
s->set_vflip(s, 1); // flip it back
s->set_brightness(s, 1); // up the brightness
s->set_saturation(s, -2); // lower the saturation
s->set_vflip(s, 1);
s->set_brightness(s, 1);
s->set_saturation(s, -2);
}

// DO NOT drop down to QVGA—keep it at UXGA or SVGA as configured above
// e.g. remove the typical line:
// s->set_framesize(s, FRAMESIZE_QVGA); // <--- REMOVED

// Start the camera server
// Start HTTPS camera server (ensure you configure TLS certs in httpd_config_t if needed)
startCameraServer();
Serial.println("Camera Ready! Visit: https://" + WiFi.localIP().toString());

Serial.print("Camera Ready! Use 'http://");
Serial.print(WiFi.localIP());
Serial.println("' to connect (MJPEG stream).");
// -----------------------------------------------------------
// Compute firmware hash and sign it.
// -----------------------------------------------------------
uint8_t fwHash[32];
if (computeFirmwareHash(fwHash)) {
gFirmwareHashHex = bytesToHexString(fwHash, sizeof(fwHash));
Serial.print("Firmware Hash: ");
Serial.println(gFirmwareHashHex);

// Sign the firmware hash using our secure key.
uint8_t signature[65]; // 65 bytes: 32 (r) + 32 (s) + 1 (v)
size_t sigLen = 0;
if (signData(fwHash, sizeof(fwHash), signature, &sigLen)) {
gSignatureHex = bytesToHexString(signature, sigLen);
Serial.println("Firmware hash signed successfully!");
Serial.print("Signature (hex): ");
Serial.println(gSignatureHex);
} else {
Serial.println("Failed to sign the firmware hash.");
}
} else {
Serial.println("Failed to compute firmware hash!");
}
}

void loop()
{
// The server runs in the background. Nothing special needed here.
delay(1000);
}
// Server runs in the background.

// -----------------------------------------------------------
// MJPEG Stream Handler (only endpoint)
// MJPEG Stream Handler (with extra headers)
// -----------------------------------------------------------
static esp_err_t stream_handler(httpd_req_t *req)
{
// Provide MJPEG "multipart/x-mixed-replace" content
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=123456789000000000000987654321";
static const char* _STREAM_BOUNDARY = "\r\n--123456789000000000000987654321\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n";

camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
uint8_t * _jpg_buf = NULL;
size_t _jpg_buf_len = 0;
char part_buf[128];

// Set appropriate content type
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if (res != ESP_OK) {
return res;
}
// Add custom headers for on-chain verification.
httpd_resp_set_hdr(req, "X-Firmware-Hash", gFirmwareHashHex.c_str());
httpd_resp_set_hdr(req, "X-Signature", gSignatureHex.c_str());

// Set content type to MJPEG.
static const char* CONTENT_TYPE = "multipart/x-mixed-replace;boundary=123456789000000000000987654321";
static const char* BOUNDARY = "\r\n--123456789000000000000987654321\r\n";
static const char* PART_FMT = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n";

esp_err_t res = httpd_resp_set_type(req, CONTENT_TYPE);
if (res != ESP_OK) return res;

// We can set a custom header if desired
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");

// Streaming loop
while (true) {
fb = esp_camera_fb_get();
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
res = ESP_FAIL;
} else {
uint8_t* jpg_buf = NULL;
size_t jpg_buf_len = 0;
if (fb->format != PIXFORMAT_JPEG) {
bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
bool converted = frame2jpg(fb, 80, &jpg_buf, &jpg_buf_len);
esp_camera_fb_return(fb);
fb = NULL;
if (!jpeg_converted) {
if (!converted) {
Serial.println("JPEG compression failed");
res = ESP_FAIL;
}
} else {
_jpg_buf_len = fb->len;
_jpg_buf = fb->buf;
jpg_buf_len = fb->len;
jpg_buf = fb->buf;
}
}

if (res == ESP_OK) {
// Send boundary
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
if (res == ESP_OK) {
// Prepare the MJPEG part header
size_t hlen = snprintf(part_buf, 128, _STREAM_PART,
_jpg_buf_len,
fb ? fb->timestamp.tv_sec : 0,
fb ? fb->timestamp.tv_usec : 0);
// Send header
res = httpd_resp_send_chunk(req, part_buf, hlen);
}
if (res == ESP_OK) {
// Send frame data
res = httpd_resp_send_chunk(req, (const char*)_jpg_buf, _jpg_buf_len);
}
if (res == ESP_OK) {
res = httpd_resp_send_chunk(req, BOUNDARY, strlen(BOUNDARY));
}
if (res == ESP_OK) {
char part_buf[128];
size_t hlen = snprintf(part_buf, sizeof(part_buf), PART_FMT,
jpg_buf_len,
fb ? fb->timestamp.tv_sec : 0,
fb ? fb->timestamp.tv_usec : 0);
res = httpd_resp_send_chunk(req, part_buf, hlen);
}
if (res == ESP_OK) {
res = httpd_resp_send_chunk(req, (const char*)jpg_buf, jpg_buf_len);
}

// Return the frame buffer back to be reused
if (fb) {
esp_camera_fb_return(fb);
fb = NULL;
_jpg_buf = NULL;
} else if (_jpg_buf) {
free(_jpg_buf);
_jpg_buf = NULL;
if (fb) {
esp_camera_fb_return(fb);
} else if (jpg_buf) {
free(jpg_buf);
}
}

if (res != ESP_OK) {
Expand All @@ -265,14 +266,10 @@ static esp_err_t stream_handler(httpd_req_t *req)
void startCameraServer()
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
// Optionally, adjust server port if needed:
// config.server_port = 80; // default

// Start the web server
// For HTTPS support, we must configure TLS certificates in config.
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
// Register only one URI: the streaming endpoint at "/"
httpd_uri_t stream_uri = {
.uri = "/", // <--- single endpoint
.uri = "/",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
Expand Down
Loading
Loading