Refactor PoC code into namespaces, add led management, improve directory structure (#2)

This commit is contained in:
Zdzislaw Goik 2022-02-27 19:20:10 +01:00 committed by GitHub
parent e9c81bad31
commit c80b665077
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 510 additions and 290 deletions

20
ESP/include/LEDManager.h Normal file
View File

@ -0,0 +1,20 @@
#include <Arduino.h>
namespace LEDManager{
enum Status {
ConnectingToWifi = 1,
ConnectingToWifiError = 2,
ConnectingToWifiSuccess = 3,
ServerError = 3,
CameraError = 4
};
extern uint8_t ledPin;
void setupLED();
void on();
void off();
void blink(unsigned int time);
void displayPattern(Status status);
}

39
ESP/include/README Normal file
View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@ -0,0 +1,6 @@
#include <WiFi.h>
#include "pinout.h"
namespace WiFiHandler {
void setupWifi();
}

View File

@ -0,0 +1,14 @@
#include "pinout.h"
#include "esp_camera.h"
#include <Arduino.h>
namespace CameraHandler{
extern sensor_t* camera_sensor;
int setupCamera();
int setCameraResolution(framesize_t framesize);
int setVFlip(int direction);
int setHFlip(int direction);
}

View File

@ -0,0 +1,9 @@
#include "esp_camera.h"
#include "esp_http_server.h"
namespace HttpdHandler{
esp_err_t stream_handler(httpd_req_t *req);
esp_err_t parse_get(httpd_req_t *req, char **obuf);
esp_err_t command_handler(httpd_req_t *req);
int startStreamServer();
}

View File

@ -1,5 +1,3 @@
#define LED_INDICATOR 33
// AI Tinker camera, the ov2650
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1

46
ESP/lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@ -16,4 +16,7 @@ monitor_speed = 115200
monitor_rts = 0
monitor_dtr = 0
build_flags = -O2
build_unflags = -Os
build_unflags = -Os
lib_deps =
esp32-camera

44
ESP/src/LEDManager.cpp Normal file
View File

@ -0,0 +1,44 @@
# include "LEDManager.h"
namespace LEDManager {
uint8_t ledPin = 33;
void setupLED(){
pinMode(ledPin, OUTPUT);
off();
}
void on(){
digitalWrite(ledPin, LOW);
}
void off(){
digitalWrite(ledPin, HIGH);
}
void blink(unsigned int time){
on();
delay(time);
off();
delay(time);
}
void displayPattern(Status status){
if(status == Status::ConnectingToWifi){
blink(1600);
delay(1600);
}
if(status == Status::ConnectingToWifiError){
for (int i = 0; i < 5; i++){
blink(1000);
delay(1000);
}
}
if(status == Status::ConnectingToWifiSuccess){
for (int i = 0; i < 2; i++){
blink(1600);
delay(1600);
}
}
}
}

75
ESP/src/cameraHandler.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "cameraHandler.h"
namespace CameraHandler{
sensor_t* camera_sensor = NULL;
int setupCamera(){
Serial.print("Setting up camera \r\n");
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;
config.pixel_format = PIXFORMAT_JPEG;
if (psramFound()){
Serial.println("Found psram, setting the UXGA image quality");
config.frame_size = FRAMESIZE_240X240;
config.jpeg_quality = 10;
config.fb_count = 2;
}else{
Serial.println("Did not find psram, setting svga quality");
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config);
camera_sensor = esp_camera_sensor_get();
if (err != ESP_OK){
Serial.printf("Camera initialization failed with error: 0x%x \r\n", err);
return -1;
}else{
Serial.println("Sucessfully initialized the camera!");
return 0;
}
}
int setCameraResolution(framesize_t framesize){
if(camera_sensor->pixformat == PIXFORMAT_JPEG){
try{
return camera_sensor->set_framesize(camera_sensor, framesize);
}catch(...){
// they sent us a malformed or unsupported framesize - rather than crash - tell them about it
return -1;
}
}
return -1;
}
int setVFlip(int direction){
return camera_sensor->set_vflip(camera_sensor, direction);
}
int setHFlip(int direction){
return camera_sensor->set_hmirror(camera_sensor, direction);
}
}

177
ESP/src/httpdHandler.cpp Normal file
View File

@ -0,0 +1,177 @@
#include <Arduino.h>
#include "cameraHandler.h"
#include "httpdHandler.h"
#define PART_BOUNDARY "123456789000000000000987654321"
namespace HttpdHandler {
httpd_handle_t camera_httpd = NULL;
httpd_handle_t control_httpd = NULL;
static const char*_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char*_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char*_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n";
esp_err_t stream_handler(httpd_req_t *req) {
camera_fb_t *fb = NULL;
struct timeval _timestamp;
esp_err_t res = ESP_OK;
size_t _jpg_buf_len = 0;
uint8_t *_jpg_buf = NULL;
char *part_buf[128];
static int64_t last_frame = 0;
if (!last_frame)
last_frame = esp_timer_get_time();
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if (res != ESP_OK)
return res;
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin; Content-Type: multipart/x-mixed-replace; boundary=123456789000000000000987654321\r\n", "*");
httpd_resp_set_hdr(req, "X-Framerate", "60");
while (true) {
fb = esp_camera_fb_get();
if (!fb){
ESP_LOGE(TAG, "Camera capture failed");
res = ESP_FAIL;
}
else{
_timestamp.tv_sec = fb->timestamp.tv_sec;
_timestamp.tv_usec = fb->timestamp.tv_usec;
if (fb->format != PIXFORMAT_JPEG){
bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
esp_camera_fb_return(fb);
fb = NULL;
if (!jpeg_converted){
ESP_LOGE(TAG, "JPEG compression failed");
res = ESP_FAIL;
}
}
else{
_jpg_buf_len = fb->len;
_jpg_buf = fb->buf;
}
}
if (res == ESP_OK){
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
if (res == ESP_OK){
size_t hlen = snprintf((char *)part_buf, 128, _STREAM_PART, _jpg_buf_len, _timestamp.tv_sec, _timestamp.tv_usec);
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
}
if (res == ESP_OK){
res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
}
if (fb){
esp_camera_fb_return(fb);
fb = NULL;
_jpg_buf = NULL;
}
else if (_jpg_buf){
free(_jpg_buf);
_jpg_buf = NULL;
}
if (res != ESP_OK){
break;
}
}
last_frame = 0;
return res;
}
esp_err_t parse_get(httpd_req_t *req, char **obuf) {
char *buf = NULL;
size_t buf_len = 0;
buf_len = httpd_req_get_url_query_len(req) + 1;
if (buf_len > 1) {
buf = (char *)malloc(buf_len);
if (!buf) {
httpd_resp_send_500(req);
return ESP_FAIL;
}
if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
*obuf = buf;
return ESP_OK;
}
free(buf);
}
httpd_resp_send_404(req);
return ESP_FAIL;
}
esp_err_t command_handler(httpd_req_t *req) {
char *buf = NULL;
char variable[32];
char value[32];
if (parse_get(req, &buf) != ESP_OK)
return ESP_FAIL;
if (httpd_query_key_value(buf, "var", variable, sizeof(variable)) != ESP_OK ||
httpd_query_key_value(buf, "val", value, sizeof(value)) != ESP_OK) {
free(buf);
httpd_resp_send_404(req);
return ESP_FAIL;
}
free(buf);
int val = atoi(value);
int res = 0;
if (!strcmp(variable, "framesize"))
res = CameraHandler::setCameraResolution((framesize_t)val);
else if (!strcmp(variable, "hmirror"))
res = CameraHandler::setHFlip(val);
else if (!strcmp(variable, "vflip"))
res = CameraHandler::setVFlip(val);
else
res = -1; // invalid command
if (res < 0)
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Command not supported");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
return httpd_resp_send(req, NULL, 0);
}
int startStreamServer(){
Serial.println("Setting up the server");
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = 3;
httpd_uri_t control_page = {
.uri = "/control/",
.method = HTTP_GET,
.handler = &command_handler,
.user_ctx = NULL
};
httpd_uri_t stream_page = {
.uri = "/",
.method = HTTP_GET,
.handler = &stream_handler,
.user_ctx = NULL
};
int streamer_status = httpd_start(&control_httpd, &config);
config.server_port += 1;
config.ctrl_port += 1;
int cmd_controller_status = httpd_start(&camera_httpd, &config);
if (streamer_status != ESP_OK || cmd_controller_status != ESP_OK)
return -1;
else {
httpd_register_uri_handler(control_httpd, &control_page);
httpd_register_uri_handler(camera_httpd, &stream_page);
Serial.println("Server is ready to serve!");
return 0;
}
}
}

25
ESP/src/main.cpp Normal file
View File

@ -0,0 +1,25 @@
#include <Arduino.h>
#include "pinout.h"
#include "WifiHandler.h"
#include "cameraHandler.h"
#include "LEDManager.h"
#include "httpdHandler.h"
void setup(){
Serial.begin(115200);
Serial.setDebugOutput(true);
Serial.println();
Serial.println("setting up led");
LEDManager::setupLED();
// todo add blink handling
CameraHandler::setupCamera();
WiFiHandler::setupWifi();
// todo add blink handling
HttpdHandler::startStreamServer();
LEDManager::on();
}
void loop(){
delay(1);
}

40
ESP/src/wifiHandler.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "WifiHandler.h"
#include "credentials.h"
#include "LEDManager.h"
namespace WiFiHandler {
void setupWifi(){
Serial.println("Initializing connection to wifi");
WiFi.begin(ssid, password);
Serial.print("connecting");
int time_spent_connecting = 0;
int connection_timeout = 6400;
int wifi_status = WiFi.status();
while (time_spent_connecting < connection_timeout || wifi_status != WL_CONNECTED){
wifi_status = WiFi.status();
Serial.print(".");
LEDManager::blink(LEDManager::Status::ConnectingToWifi);
time_spent_connecting += 1600;
delay(1600);
}
if(wifi_status == WL_CONNECTED){
LEDManager::blink(LEDManager::Status::ConnectingToWifiSuccess);
delay(1600);
Serial.print("\n\rWiFi connected\n\r");
Serial.print("ESP will be streaming under 'http://");
Serial.print(WiFi.localIP());
Serial.print(":81/\r\n");
Serial.print("ESP will be accepting commands under 'http://");
Serial.print(WiFi.localIP());
Serial.print(":80/control\r\n");
}
else{
LEDManager::blink(LEDManager::Status::ConnectingToWifiError);
return;
}
}
}

11
ESP/test/README Normal file
View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html

View File

@ -1,51 +0,0 @@
#include "pinout.h"
#include "esp_camera.h"
#include <Arduino.h>
void setupCamera(){
Serial.print("Setting up camera \r\n");
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;
config.pixel_format = PIXFORMAT_JPEG;
if (psramFound()){
Serial.println("Found psram, setting the UXGA image quality");
config.frame_size = FRAMESIZE_240X240;
config.jpeg_quality = 10;
config.fb_count = 2;
}else{
Serial.println("Did not find psram, setting svga quality");
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK){
Serial.printf("Camera initialization failed with error: 0x%x \r\n", err);
return;
}else{
Serial.println("Sucessfully initialized the camera!");
}
}

View File

@ -1,185 +0,0 @@
#include <Arduino.h>
#include "esp_http_server.h"
#include "esp_camera.h"
#define PART_BOUNDARY "123456789000000000000987654321"
httpd_handle_t camera_httpd = NULL;
httpd_handle_t control_httpd = NULL;
static const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char *_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n";
static esp_err_t stream_handler(httpd_req_t *req){
camera_fb_t *fb = NULL;
struct timeval _timestamp;
esp_err_t res = ESP_OK;
size_t _jpg_buf_len = 0;
uint8_t *_jpg_buf = NULL;
char *part_buf[128];
static int64_t last_frame = 0;
if (!last_frame)
last_frame = esp_timer_get_time();
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if (res != ESP_OK)
return res;
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin; Content-Type: multipart/x-mixed-replace; boundary=123456789000000000000987654321\r\n", "*");
httpd_resp_set_hdr(req, "X-Framerate", "60");
while (true) {
fb = esp_camera_fb_get();
if (!fb){
ESP_LOGE(TAG, "Camera capture failed");
res = ESP_FAIL;
}
else{
_timestamp.tv_sec = fb->timestamp.tv_sec;
_timestamp.tv_usec = fb->timestamp.tv_usec;
if (fb->format != PIXFORMAT_JPEG){
bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
esp_camera_fb_return(fb);
fb = NULL;
if (!jpeg_converted){
ESP_LOGE(TAG, "JPEG compression failed");
res = ESP_FAIL;
}
}
else{
_jpg_buf_len = fb->len;
_jpg_buf = fb->buf;
}
}
if (res == ESP_OK){
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
if (res == ESP_OK){
size_t hlen = snprintf((char *)part_buf, 128, _STREAM_PART, _jpg_buf_len, _timestamp.tv_sec, _timestamp.tv_usec);
res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
}
if (res == ESP_OK){
res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
}
if (fb){
esp_camera_fb_return(fb);
fb = NULL;
_jpg_buf = NULL;
}
else if (_jpg_buf){
free(_jpg_buf);
_jpg_buf = NULL;
}
if (res != ESP_OK){
break;
}
}
last_frame = 0;
return res;
}
static esp_err_t parse_get(httpd_req_t *req, char **obuf)
{
char *buf = NULL;
size_t buf_len = 0;
buf_len = httpd_req_get_url_query_len(req) + 1;
if (buf_len > 1) {
buf = (char *)malloc(buf_len);
if (!buf) {
httpd_resp_send_500(req);
return ESP_FAIL;
}
if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
*obuf = buf;
return ESP_OK;
}
free(buf);
}
httpd_resp_send_404(req);
return ESP_FAIL;
}
static esp_err_t command_handler(httpd_req_t *req) {
char *buf = NULL;
char variable[32];
char value[32];
if (parse_get(req, &buf) != ESP_OK) {
return ESP_FAIL;
}
if (httpd_query_key_value(buf, "var", variable, sizeof(variable)) != ESP_OK ||
httpd_query_key_value(buf, "val", value, sizeof(value)) != ESP_OK) {
free(buf);
httpd_resp_send_404(req);
return ESP_FAIL;
}
free(buf);
// debug
Serial.printf("%s %s \r\n", variable, value);
sensor_t *s = esp_camera_sensor_get();
int val = atoi(value);
ESP_LOGI(TAG, "%s = %d", variable, val);
int res = 0;
if (!strcmp(variable, "framesize")) {
if (s->pixformat == PIXFORMAT_JPEG) {
res = s->set_framesize(s, (framesize_t)val);
}
}
else if (!strcmp(variable, "hmirror"))
res = s->set_hmirror(s, val);
else if (!strcmp(variable, "vflip"))
res = s->set_vflip(s, val);
else {
// invalid command
res = -1;
}
if (res < 0){
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Command not supported");
}
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
return httpd_resp_send(req, NULL, 0);
}
void startStreamServer(){
Serial.println("Setting up the server");
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = 3;
httpd_uri_t control_page = {
.uri = "/control/",
.method = HTTP_GET,
.handler = &command_handler,
.user_ctx = NULL
};
httpd_uri_t stream_page = {
.uri = "/",
.method = HTTP_GET,
.handler = &stream_handler,
.user_ctx = NULL
};
if (httpd_start(&control_httpd, &config) == ESP_OK)
httpd_register_uri_handler(control_httpd, &control_page);
config.server_port += 1;
config.ctrl_port += 1;
if (httpd_start(&camera_httpd, &config) == ESP_OK)
httpd_register_uri_handler(camera_httpd, &stream_page);
Serial.println("Server is ready to serve!");
}

View File

@ -1,25 +0,0 @@
#include <Arduino.h>
#include "pinout.h"
void setupCamera();
void setupWifi();
void startStreamServer();
void setup(){
Serial.begin(115200);
Serial.setDebugOutput(true);
Serial.println();
pinMode(LED_INDICATOR, OUTPUT);
setupCamera();
setupWifi();
startStreamServer();
// for indicating that everything is working
digitalWrite(LED_INDICATOR, HIGH);
}
void loop(){
delay(1);
}

View File

@ -1,26 +0,0 @@
#include <WiFi.h>
#include "credentials.h"
#include "pinout.h"
void setupWifi(){
Serial.println("Initializing connection to wifi");
WiFi.begin(ssid, password);
Serial.print("connecting");
while (WiFi.status() != WL_CONNECTED){
digitalWrite(LED_INDICATOR, LOW);
delay(800);
digitalWrite(LED_INDICATOR, HIGH);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("ESP will be streaming under 'http://");
Serial.print(WiFi.localIP());
Serial.print(":81/\r\n");
Serial.print("ESP will be accepting commands under 'http://");
Serial.print(WiFi.localIP());
Serial.print(":80/control\r\n");
}