Прошло 2 года.
This commit is contained in:
6
lib/epdiy/examples/http-server/CMakeLists.txt
Normal file
6
lib/epdiy/examples/http-server/CMakeLists.txt
Normal file
@@ -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
lib/epdiy/examples/http-server/README.md
Normal file
34
lib/epdiy/examples/http-server/README.md
Normal file
@@ -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
lib/epdiy/examples/http-server/component.mk
Normal file
4
lib/epdiy/examples/http-server/component.mk
Normal file
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
8
lib/epdiy/examples/http-server/main/CMakeLists.txt
Normal file
8
lib/epdiy/examples/http-server/main/CMakeLists.txt
Normal file
@@ -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
lib/epdiy/examples/http-server/main/epd.c
Normal file
44
lib/epdiy/examples/http-server/main/epd.c
Normal file
@@ -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
lib/epdiy/examples/http-server/main/epd.h
Normal file
17
lib/epdiy/examples/http-server/main/epd.h
Normal file
@@ -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
lib/epdiy/examples/http-server/main/main.c
Normal file
149
lib/epdiy/examples/http-server/main/main.c
Normal file
@@ -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
lib/epdiy/examples/http-server/main/server.c
Normal file
46
lib/epdiy/examples/http-server/main/server.c
Normal file
@@ -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
lib/epdiy/examples/http-server/main/server.h
Normal file
19
lib/epdiy/examples/http-server/main/server.h
Normal file
@@ -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
lib/epdiy/examples/http-server/main/settings.h
Normal file
9
lib/epdiy/examples/http-server/main/settings.h
Normal file
@@ -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 */
|
||||
0
lib/epdiy/examples/http-server/sdkconfig.defaults
Normal file
0
lib/epdiy/examples/http-server/sdkconfig.defaults
Normal file
1500
lib/epdiy/examples/http-server/sdkconfig.defaults.esp32
Normal file
1500
lib/epdiy/examples/http-server/sdkconfig.defaults.esp32
Normal file
File diff suppressed because it is too large
Load Diff
1455
lib/epdiy/examples/http-server/sdkconfig.defaults.esp32s3
Normal file
1455
lib/epdiy/examples/http-server/sdkconfig.defaults.esp32s3
Normal file
File diff suppressed because it is too large
Load Diff
107
lib/epdiy/examples/http-server/send_image.py
Executable file
107
lib/epdiy/examples/http-server/send_image.py
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import requests
|
||||
from typing import NamedTuple
|
||||
import PIL
|
||||
import PIL.Image
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("hostname")
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
clear_parser = subparsers.add_parser("clear")
|
||||
draw_parser = subparsers.add_parser("draw")
|
||||
draw_parser.add_argument("-c", "--clear", action="store_true")
|
||||
draw_parser.add_argument("file")
|
||||
info_praser = subparsers.add_parser("info")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def clear(hostname):
|
||||
requests.post(f"http://{hostname}/clear").raise_for_status()
|
||||
|
||||
|
||||
class EpdInfo(NamedTuple):
|
||||
width: int
|
||||
height: int
|
||||
temperature: int
|
||||
|
||||
@classmethod
|
||||
def from_response(cls, resp):
|
||||
return cls(
|
||||
width=int(resp.headers["width"]),
|
||||
height=int(resp.headers["height"]),
|
||||
temperature=int(resp.headers["temperature"]),
|
||||
)
|
||||
|
||||
|
||||
class Dimensions(NamedTuple):
|
||||
width: int
|
||||
height: int
|
||||
|
||||
|
||||
def info(hostname):
|
||||
resp = requests.get(f"http://{hostname}")
|
||||
resp.raise_for_status()
|
||||
return EpdInfo.from_response(resp)
|
||||
|
||||
|
||||
def image_refit(image: PIL.Image, bounder: Dimensions) -> PIL.Image:
|
||||
bounder_ratio = bounder.width / bounder.height
|
||||
image_width, image_height = image.size
|
||||
|
||||
image_width_by_height = int(image_height * bounder_ratio)
|
||||
image_height_by_width = int(image_width / bounder_ratio)
|
||||
if image_width > image_width_by_height:
|
||||
new_dimensions = Dimensions(image_width_by_height, image_height)
|
||||
else:
|
||||
new_dimensions = Dimensions(image_width, image_height_by_width)
|
||||
return PIL.ImageOps.fit(image, new_dimensions)
|
||||
|
||||
|
||||
def convert_8bit_to_4bit(bytestring):
|
||||
fourbit = []
|
||||
for i in range(0, len(bytestring), 2):
|
||||
first_nibble = int(bytestring[i] / 17)
|
||||
second_nibble = int(bytestring[i + 1] / 17)
|
||||
fourbit += [first_nibble << 4 | second_nibble]
|
||||
fourbit = bytes(fourbit)
|
||||
return fourbit
|
||||
|
||||
|
||||
def draw(hostname, filename, clear):
|
||||
inf = info(hostname)
|
||||
img = PIL.Image.open(filename)
|
||||
img = image_refit(img, Dimensions(width=inf.width, height=inf.height))
|
||||
img = img.resize((inf.width, inf.height))
|
||||
img = img.convert("L")
|
||||
img_bytes = convert_8bit_to_4bit(img.tobytes())
|
||||
requests.post(
|
||||
f"http://{hostname}/draw",
|
||||
headers={
|
||||
"width": str(inf.width),
|
||||
"height": str(inf.height),
|
||||
"x": "0",
|
||||
"y": "0",
|
||||
"clear": "1" if clear else "0",
|
||||
},
|
||||
data=img_bytes,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
if args.command == "clear":
|
||||
clear(args.hostname)
|
||||
elif args.command == "info":
|
||||
print(info(args.hostname))
|
||||
elif args.command == "draw":
|
||||
draw(args.hostname, args.file, args.clear)
|
||||
else:
|
||||
raise Exception(f"Unknown command {args.command}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user