Skip to content

Commit

Permalink
Add HTTP Server Example
Browse files Browse the repository at this point in the history
Runs an HTTP server that will draw images on the screen. Useful for
setting up a small digital frame that can be remotely controlled.
Has a friendly Python client.
  • Loading branch information
BackSlasher authored Nov 19, 2024
1 parent 8d2822f commit 31b8b24
Show file tree
Hide file tree
Showing 14 changed files with 4,853 additions and 0 deletions.
6 changes: 6 additions & 0 deletions examples/http-server/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.5)

set(EXTRA_COMPONENT_DIRS "../../")

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(http-server)
34 changes: 34 additions & 0 deletions examples/http-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# HTTP server

Runs an HTTP server that will draw images on the screen.
Useful for setting up a small digital frame that can be remotely controlled.

## Config
1. In `main/server.h`, edit `WIFI_SSID` and `WIFI_PASSWORD` to match your wifi config
2. In `main/main.c`, edit `n_epd_setup` to refer to the right EPD screen

## Running
flash (`idf.py flash`), then connect the EPDiy to a power source (computer is fine).
The endpoints are:
1. `GET /`, prints the screen temp / height / width as headers
2. `POST /clear`, clears the screen
3. `POST /draw`, expects:
1. a body that is a binary stream already encoded to EPDiy's standards (like the one in `dragon.h`).
2. Headers `width`, `height`
3. Optional headers `x`,`y` (default to 0)
4. Optional header `clear`, if set to nonzero integer will force-clear the screen before drawing

## Helper script
`send_image.py` is a friendlier client.
```bash
$ ./send_image.py ESP_IP info
EpdInfo(width=1024, height=768, temperature=20)

$ ./send_image.py ESP_IP clear
# Clears the screen

$ ./send_image.y ESP_IP draw /tmp/spooder-man.png
# Draws on screen
```
Thanks to argparse, all arguments are visible with `--help`.
Requires `requests` and `PIL` (or Pillow)
4 changes: 4 additions & 0 deletions examples/http-server/component.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
8 changes: 8 additions & 0 deletions examples/http-server/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
set(
app_sources "epd.c" "server.c" "main.c"
)

idf_component_register(
SRCS ${app_sources}
REQUIRES epdiy esp_wifi nvs_flash esp_http_server esp_netif
)
44 changes: 44 additions & 0 deletions examples/http-server/main/epd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "epd.h"

static EpdiyHighlevelState hl;
static EpdData data;

static inline void checkError(enum EpdDrawError err) {
if (err != EPD_DRAW_SUCCESS) {
ESP_LOGE("demo", "draw error: %X", err);
}
}

EpdData n_epd_data() {
return data;
}

void n_epd_setup(const EpdDisplay_t* display) {
epd_init(&epd_board_v7, display, EPD_LUT_64K);
epd_set_vcom(1560);
hl = epd_hl_init(EPD_BUILTIN_WAVEFORM);
epd_set_rotation(EPD_ROT_LANDSCAPE);
data.width = epd_rotated_display_width();
data.height = epd_rotated_display_height();
data.temperature = epd_ambient_temperature();
}

void n_epd_clear() {
epd_poweron();
epd_fullclear(&hl, data.temperature);
epd_poweroff();
}

void n_epd_draw(uint8_t* content, int x, int y, int width, int height) {
uint8_t* fb = epd_hl_get_framebuffer(&hl);
EpdRect area = {
.x = x,
.y = y,
.width = width,
.height = height,
};
epd_draw_rotated_image(area, content, fb);
epd_poweron();
checkError(epd_hl_update_screen(&hl, MODE_GC16, data.temperature));
epd_poweroff();
}
17 changes: 17 additions & 0 deletions examples/http-server/main/epd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef EPD_H
#define EPD_H

#include "epdiy.h"
#include <esp_log.h>

typedef struct {
int width;
int height;
int temperature;
} EpdData;

EpdData n_epd_data();
void n_epd_setup();
void n_epd_clear();
void n_epd_draw(uint8_t* content, int x, int y, int width, int height);
#endif /* EPD_H */
149 changes: 149 additions & 0 deletions examples/http-server/main/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#include "server.h"
#include "esp_http_server.h"
#include "epd.h"
#include "esp_heap_caps.h"
#include "settings.h"

#define WRITE_HEADER(req, buffer, name, format, src) \
sprintf(buffer, format, src); \
ESP_ERROR_CHECK(httpd_resp_set_hdr(req, name, buffer));
static esp_err_t http_index(httpd_req_t* req) {
EpdData data = n_epd_data();
char width[20], height[20], temperature[20];
WRITE_HEADER(req, width, "width", "%d", data.width);
WRITE_HEADER(req, height, "height", "%d", data.height);
WRITE_HEADER(req, temperature, "temperature", "%d", data.temperature);
const char* response = "Hello! Check headers\n";
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_status(req, "200");
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}

esp_err_t http_clear(httpd_req_t* req) {
ESP_LOGI(__FUNCTION__, "Clear\n");
n_epd_clear();
const char* response = "Cleared\n";
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_status(req, "200");
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}

esp_err_t http_draw(httpd_req_t* req) {
// optional headers: x,y. Default to 0
// required headers: height, width
// Content should be a stream of special bytes - we're reading 4 bits at a time.
int x, y, width, height, clear;
char header[20];
memset(header, 0, 20);
if (httpd_req_get_hdr_value_str(req, "clear", header, 20) == ESP_OK) {
sscanf(header, "%d", &clear);
} else {
clear = 0;
}
if (httpd_req_get_hdr_value_str(req, "x", header, 20) == ESP_OK) {
sscanf(header, "%d", &x);
} else {
x = 0;
}
if (httpd_req_get_hdr_value_str(req, "y", header, 20) == ESP_OK) {
sscanf(header, "%d", &y);
} else {
y = 0;
}
if (httpd_req_get_hdr_value_str(req, "width", header, 20) == ESP_OK) {
sscanf(header, "%d", &width);
} else {
char response[60];
sprintf(response, "Missing header width");
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_status(req, "400");
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
if (httpd_req_get_hdr_value_str(req, "height", header, 20) == ESP_OK) {
sscanf(header, "%d", &height);
} else {
char response[60];
sprintf(response, "Missing header height");
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_status(req, "400");
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}

// READING STREAM
int req_size = req->content_len;
char* content = (char*)heap_caps_malloc(req_size, MALLOC_CAP_SPIRAM);
if (content == NULL) {
char msg[50];
sprintf(msg, "Failed to allocate %d chars\n", req_size);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, msg);
return ESP_ERR_INVALID_ARG;
}
int current_pos = 0;
int amount_recieved;
while ((amount_recieved = httpd_req_recv(req, (content + current_pos), req_size)) > 0) {
ESP_LOGI(__FUNCTION__, "Read %d bytes\n", amount_recieved);
current_pos += amount_recieved;
}
if (amount_recieved < 0) {
char msg[50];
heap_caps_free(content);
ESP_LOGE(msg, "Failed to read byets. Error code %d\n", amount_recieved);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, msg);
return ESP_ERR_INVALID_ARG;
}
ESP_LOGI(__FUNCTION__, "Done reading %d bytes out of %d\n", current_pos, req_size);

if (clear) {
n_epd_clear();
}
n_epd_draw(((uint8_t*)content), x, y, width, height);
heap_caps_free(content);

// Done reading
char response[100];
sprintf(
response, "x %d, y %d, width %d, height %d, byte count %d\n", x, y, width, height, req_size
);
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_status(req, "200");
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}

void register_paths(httpd_handle_t server) {
{
httpd_uri_t uri
= { .uri = "/", .method = HTTP_GET, .handler = http_index, .user_ctx = NULL };
httpd_register_uri_handler(server, &uri);
}
{
httpd_uri_t uri
= { .uri = "/clear", .method = HTTP_POST, .handler = http_clear, .user_ctx = NULL };
httpd_register_uri_handler(server, &uri);
}
{
httpd_uri_t uri
= { .uri = "/draw", .method = HTTP_POST, .handler = http_draw, .user_ctx = NULL };
httpd_register_uri_handler(server, &uri);
}
}

void app_main(void) {
// Initialize NVS, needed for wifi
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);

httpd_handle_t server = get_server();
if (server != NULL) {
register_paths(server);
}
n_epd_setup(&SCREEN_MODEL);
}
46 changes: 46 additions & 0 deletions examples/http-server/main/server.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "server.h"

httpd_handle_t get_server(void);

static void wifi_init_sta(void) {
// Initialize the ESP-NETIF
esp_netif_init();
esp_event_loop_create_default();

// Create default event loop
esp_netif_create_default_wifi_sta();

// Initialize the Wi-Fi driver
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));

// Set Wi-Fi mode to station
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));

// Configure Wi-Fi connection
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASSWORD,
},
};

// Set the Wi-Fi configuration
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_connect());
}

httpd_handle_t start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.lru_purge_enable = true;

httpd_handle_t server = NULL;
ESP_ERROR_CHECK(httpd_start(&server, &config));
return server;
}

httpd_handle_t get_server(void) {
wifi_init_sta();
return start_webserver();
}
19 changes: 19 additions & 0 deletions examples/http-server/main/server.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef SERVER_H
#define SERVER_H

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_http_server.h"

#include "settings.h"

httpd_handle_t get_server(void);

#endif /* SERVER_H */
9 changes: 9 additions & 0 deletions examples/http-server/main/settings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef SETTINGS_H
#define SETTINGS_H

#define WIFI_SSID "best ssid" // Replace with your Wi-Fi SSID
#define WIFI_PASSWORD "great password much wow" // Replace with your Wi-Fi password

#define SCREEN_MODEL ED060XC3

#endif /* SETTINGS_H */
Loading

0 comments on commit 31b8b24

Please sign in to comment.