From 3bdf60aed54a6171bc6458a7d578cd65f1fbb092 Mon Sep 17 00:00:00 2001 From: Sadeep Madurange Date: Tue, 3 Oct 2023 07:28:18 +0800 Subject: Improve pdf rasterization and TLS. --- main/CMakeLists.txt | 1 + main/epd.c | 30 +++++- main/epd.h | 2 +- main/main.c | 262 ++++++++++++++++++++++++++++++++++++++++++++-------- main/util.h | 2 +- pdftoebm.py | 92 ++++++++++++++++++ pdftool.py | 98 -------------------- 7 files changed, 344 insertions(+), 143 deletions(-) create mode 100644 pdftoebm.py delete mode 100644 pdftool.py diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 62589d9..81b367e 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,2 +1,3 @@ idf_component_register(SRCS "main.c" "epd.c" "util.c" + EMBED_TXTFILES git_srht_cert.pem INCLUDE_DIRS ".") diff --git a/main/epd.c b/main/epd.c index 7398214..b59bf60 100644 --- a/main/epd.c +++ b/main/epd.c @@ -21,7 +21,7 @@ #define EPD_MOSI_PIN GPIO_NUM_23 #define EPD_BUSY_PIN GPIO_NUM_4 -static const char* TAG = "EPD"; +static const char* TAG = "epd"; static spi_device_handle_t spi = NULL; @@ -93,6 +93,7 @@ static void send_cmd(uint8_t cmd) t.length = 8; t.tx_buffer = &cmd; t.user = (void*)0; + ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t)); } @@ -104,6 +105,7 @@ static void send_data(uint8_t data) t.length = 8; t.tx_buffer = &data; t.user = (void*)1; + ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t)); } @@ -112,10 +114,12 @@ static inline void wait_until_idle(void) int busy; ESP_LOGI(TAG, "busy..."); + do { send_cmd(0x71); busy = gpio_get_level(EPD_BUSY_PIN); } while(busy == 0); + ESP_LOGI(TAG, "ready"); delay_ms(20); } @@ -124,8 +128,10 @@ static inline void reset(void) { ESP_ERROR_CHECK(gpio_set_level(EPD_RST_PIN, 1)); delay_ms(200); + ESP_ERROR_CHECK(gpio_set_level(EPD_RST_PIN, 0)); delay_ms(2); + ESP_ERROR_CHECK(gpio_set_level(EPD_RST_PIN, 1)); delay_ms(200); } @@ -142,7 +148,9 @@ static inline void config_lut(uint8_t cmd, const uint8_t *lut) static inline void gpio_init(void) { gpio_config_t io_cfg = { - .pin_bit_mask = ((1ULL << EPD_DC_PIN) | (1ULL << EPD_RST_PIN) | (1ULL << EPD_BUSY_PIN)), + .pin_bit_mask = ((1ULL << EPD_DC_PIN) | + (1ULL << EPD_RST_PIN) | + (1ULL << EPD_BUSY_PIN)), .pull_up_en = true }; @@ -150,6 +158,7 @@ static inline void gpio_init(void) ESP_ERROR_CHECK(gpio_set_direction(EPD_DC_PIN, GPIO_MODE_OUTPUT)); ESP_ERROR_CHECK(gpio_set_direction(EPD_RST_PIN, GPIO_MODE_OUTPUT)); ESP_ERROR_CHECK(gpio_set_direction(EPD_BUSY_PIN, GPIO_MODE_INPUT)); + delay_ms(500); } @@ -163,7 +172,10 @@ static void spi_init(void) .quadhd_io_num = -1, .max_transfer_sz = 8 }; - ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus_cfg, SPI_DMA_CH_AUTO)); + + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, + &bus_cfg, + SPI_DMA_CH_AUTO)); spi_device_interface_config_t dev_cfg = { .clock_speed_hz = 10 * 1000 * 1000, @@ -172,6 +184,7 @@ static void spi_init(void) .queue_size = 1, .pre_cb = spi_pre_cb_handler }; + ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &dev_cfg, &spi)); } @@ -242,6 +255,7 @@ static inline void refresh() { send_cmd(0x12); delay_ms(100); + wait_until_idle(); } @@ -249,7 +263,9 @@ void epd_clear(void) { uint16_t i, width, height; - width = (EPD_SCREEN_WIDTH % 8 == 0) ? (EPD_SCREEN_WIDTH / 8 ) : (EPD_SCREEN_WIDTH / 8 + 1); + width = (EPD_SCREEN_WIDTH % 8 == 0) ? + (EPD_SCREEN_WIDTH / 8 ) : + (EPD_SCREEN_WIDTH / 8 + 1); height = EPD_SCREEN_HEIGHT; send_cmd(0x10); @@ -267,7 +283,9 @@ void epd_draw(const uint8_t pb[48000]) { uint32_t i, j, width, height; - width = (EPD_SCREEN_WIDTH % 8 == 0) ? (EPD_SCREEN_WIDTH / 8 ) : (EPD_SCREEN_WIDTH / 8 + 1); + width = (EPD_SCREEN_WIDTH % 8 == 0) ? + (EPD_SCREEN_WIDTH / 8 ) : + (EPD_SCREEN_WIDTH / 8 + 1); height = EPD_SCREEN_HEIGHT; send_cmd(0x13); @@ -282,7 +300,9 @@ void epd_draw(const uint8_t pb[48000]) void epd_sleep(void) { send_cmd(0x02); + wait_until_idle(); + send_cmd(0x07); send_data(0xA5); } diff --git a/main/epd.h b/main/epd.h index 1cd254f..fa124ab 100644 --- a/main/epd.h +++ b/main/epd.h @@ -6,4 +6,4 @@ void epd_clear(void); void epd_draw(const uint8_t pb[48000]); void epd_sleep(void); -#endif +#endif /* EPD_H */ diff --git a/main/main.c b/main/main.c index 526a134..3201f00 100644 --- a/main/main.c +++ b/main/main.c @@ -1,21 +1,110 @@ #include +#include #include #include -#include #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include "epd.h" #include "util.h" -#include "doc.h" -#define IO_PG_PREV GPIO_NUM_21 -#define IO_PG_NEXT GPIO_NUM_22 -#define IO_INTR_FLAG_DEFAULT 0 +#define PAGE_LEN 3 +#define PAGE_SIZE 48000 + +#define IO_PAGE_PREV GPIO_NUM_21 +#define IO_PAGE_NEXT GPIO_NUM_22 +#define IO_INTR_FLAG_DEFAULT 0 + +#define WIFI_SSID CONFIG_ESP_WIFI_SSID +#define WIFI_PASSWORD CONFIG_ESP_WIFI_PASSWORD +#define WIFI_MAX_RETRY CONFIG_ESP_MAXIMUM_RETRY + +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_ERROR_BIT BIT1 + +#define HTTP_URL_BASE CONFIG_HTTP_BASE_URL +#define HTTP_URL_SUFFIX ".ebm" + +extern const char git_srht_cert_pem_start[] asm("_binary_git_srht_cert_pem_start"); +extern const char git_srht_cert_pem_end[] asm("_binary_git_srht_cert_pem_end"); static const char* TAG = "app"; -static size_t page = 0; +static size_t page_num = 0; +static uint8_t *pages[PAGE_LEN]; + +static int http_get_page(size_t page_n, char buf[PAGE_SIZE]) +{ + int rc; + char *url; + esp_http_client_handle_t client; + + rc = 0; + url = NULL; + client = NULL; + + int url_len = strlen(HTTP_URL_BASE) + + snprintf(NULL, 0,"%02d", page_n) + + strlen(HTTP_URL_SUFFIX) + 1; + + if ((url = malloc(sizeof(char) * url_len)) == NULL) { + ESP_LOGE(TAG, "malloc() failed for URL"); + goto exit; + } + + snprintf(url, url_len, "%s%02d%s", HTTP_URL_BASE, page_n, + HTTP_URL_SUFFIX); + + ESP_LOGI(TAG, "downloading, URL = %s", url); + + esp_http_client_config_t config = { + .url = url, + .timeout_ms = 5000, + .disable_auto_redirect = true, + .max_authorization_retries = -1, + .cert_pem = git_srht_cert_pem_start + }; + + client = esp_http_client_init(&config); + + esp_err_t err = esp_http_client_open(client, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "HTTP connection error: %s", esp_err_to_name(err)); + goto exit; + } + + esp_http_client_fetch_headers(client); + + int read_len = esp_http_client_read(client, buf, PAGE_SIZE); + if (read_len <= 0) { + ESP_LOGE(TAG, "HTTP response read error"); + goto exit; + } + + ESP_LOGI(TAG, "HTTP response status = %d, content length = %" PRId64, + esp_http_client_get_status_code(client), + esp_http_client_get_content_length(client)); + + rc = read_len == PAGE_SIZE; + +exit: + free(url); + esp_http_client_close(client); + esp_http_client_cleanup(client); + + return rc; +} + static QueueHandle_t gpio_evt_queue = NULL; static void gpio_task(void* arg) @@ -24,65 +113,162 @@ static void gpio_task(void* arg) for(;;) { if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) { - if (io_num == IO_PG_NEXT) { - if (page < data_len - 1) { - epd_draw(data[++page]); - gpio_intr_enable(IO_PG_NEXT); - gpio_intr_enable(IO_PG_PREV); - } else { - epd_clear(); - epd_sleep(); + if (io_num == IO_PAGE_NEXT) { + page_num++; + //epd_draw(pages[page_num % PAGE_LEN]); + if(!http_get_page(page_num + 2, + (char *) pages[(page_num + 1) % PAGE_LEN])) { + page_num--; } } - else if (io_num == IO_PG_PREV) { - if (page > 0) { - epd_draw(data[--page]); - gpio_intr_enable(IO_PG_NEXT); - gpio_intr_enable(IO_PG_PREV); - } else { - epd_clear(); - epd_sleep(); + else if (io_num == IO_PAGE_PREV && page_num > 0) { + page_num--; + //epd_draw(pages[page_num % PAGE_LEN]); + if(!http_get_page(page_num + 1, + (char *) pages[(page_num - 1) % PAGE_LEN])) { + page_num++; } } - else { + else delay_ms(500); - gpio_intr_enable(IO_PG_NEXT); - gpio_intr_enable(IO_PG_PREV); - } + + gpio_intr_enable(IO_PAGE_NEXT); + gpio_intr_enable(IO_PAGE_PREV); } } } static void gpio_isr_handler(void *arg) { - gpio_intr_disable(IO_PG_PREV); - gpio_intr_disable(IO_PG_NEXT); + gpio_intr_disable(IO_PAGE_PREV); + gpio_intr_disable(IO_PAGE_NEXT); + uint32_t gpio_num = (uint32_t) arg; xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); } +static int wifi_retry_num = 0; +static EventGroupHandle_t wifi_evt_group; + +static void wifi_evt_handler(void *arg, + esp_event_base_t eb, int32_t id, + void *data) +{ + if (eb == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) { + if (wifi_retry_num < WIFI_MAX_RETRY) { + esp_wifi_connect(); + wifi_retry_num++; + ESP_LOGI(TAG, "trying to connect to AP..."); + } else { + ESP_LOGE(TAG,"connection to AP failed"); + xEventGroupSetBits(wifi_evt_group, WIFI_ERROR_BIT); + } + } else if (eb == IP_EVENT && id == IP_EVENT_STA_GOT_IP) { + ip_event_got_ip_t* evt = (ip_event_got_ip_t*) data; + ESP_LOGI(TAG, "connected to AP with ip:" IPSTR, IP2STR(&evt->ip_info.ip)); + wifi_retry_num = 0; + xEventGroupSetBits(wifi_evt_group, WIFI_CONNECTED_BIT); + } +} + +static inline void wifi_connect(void) +{ + wifi_evt_group = xEventGroupCreate(); + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + esp_event_handler_instance_t any_id; + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, + ESP_EVENT_ANY_ID, + &wifi_evt_handler, + NULL, + &any_id)); + + esp_event_handler_instance_t got_ip; + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, + IP_EVENT_STA_GOT_IP, + &wifi_evt_handler, + NULL, + &got_ip)); + + wifi_config_t wifi_config = { + .sta = { + .ssid = WIFI_SSID, + .password = WIFI_PASSWORD, + .threshold.authmode = WIFI_AUTH_WPA2_PSK + }, + }; + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); + + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_ERROR_CHECK(esp_wifi_connect()); + + xEventGroupWaitBits(wifi_evt_group, + WIFI_CONNECTED_BIT | WIFI_ERROR_BIT, + pdFALSE, + pdFALSE, + portMAX_DELAY); + + ESP_LOGI(TAG, "wifi station initialized"); +} + void app_main(void) { - epd_init(); - ESP_LOGI(TAG, "initialized e-paper display"); + esp_err_t rc = nvs_flash_init(); + if (rc == ESP_ERR_NVS_NO_FREE_PAGES || rc == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ESP_ERROR_CHECK(nvs_flash_init()); + } + + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(esp_netif_init()); gpio_config_t io_cfg = { - .pin_bit_mask = ((1ULL << IO_PG_PREV) | (1ULL << IO_PG_NEXT)), + .pin_bit_mask = ((1ULL << IO_PAGE_PREV) | (1ULL << IO_PAGE_NEXT)), .mode = GPIO_MODE_INPUT, .pull_up_en = true, .intr_type = GPIO_INTR_NEGEDGE }; + ESP_ERROR_CHECK(gpio_config(&io_cfg)); gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t)); - xTaskCreate(gpio_task, "gpio_task", 2048, NULL, 10, NULL); + xTaskCreate(gpio_task, "gpio_task", 4096, NULL, 10, NULL); + + ESP_ERROR_CHECK(gpio_set_direction(IO_PAGE_PREV, GPIO_MODE_INPUT)); + ESP_ERROR_CHECK(gpio_set_direction(IO_PAGE_NEXT, GPIO_MODE_INPUT)); - gpio_install_isr_service(IO_INTR_FLAG_DEFAULT); - gpio_isr_handler_add(IO_PG_PREV, gpio_isr_handler, (void *) IO_PG_PREV); - gpio_isr_handler_add(IO_PG_NEXT, gpio_isr_handler, (void *) IO_PG_NEXT); + wifi_connect(); - epd_clear(); - delay_ms(500); - epd_draw(data[0]); + //epd_init(); + //epd_clear(); + //delay_ms(500); + + pages[0] = malloc(sizeof(uint8_t) * PAGE_SIZE); + pages[1] = malloc(sizeof(uint8_t) * PAGE_SIZE); + pages[2] = malloc(sizeof(uint8_t) * PAGE_SIZE); + + if (pages[0] && pages[1] && pages[2]) { + if (http_get_page(page_num + 1, + (char *) pages[page_num % PAGE_LEN])) { + //epd_draw(pages[page_num % PAGE_LEN]); + if (!http_get_page(page_num + 2, + (char *) pages[(page_num + 1) % PAGE_LEN])) { + page_num = -1; + } + + gpio_install_isr_service(IO_INTR_FLAG_DEFAULT); + gpio_isr_handler_add(IO_PAGE_PREV, gpio_isr_handler, (void *) IO_PAGE_PREV); + gpio_isr_handler_add(IO_PAGE_NEXT, gpio_isr_handler, (void *) IO_PAGE_NEXT); + + ESP_LOGI(TAG, "GPIO for user input initialized"); + } + } else { + ESP_LOGE(TAG, "malloc() failed for pages"); + } } diff --git a/main/util.h b/main/util.h index 2a22daa..7156c8f 100644 --- a/main/util.h +++ b/main/util.h @@ -3,4 +3,4 @@ void delay_ms(unsigned int t); -#endif +#endif /* EPD_UTIL_H */ diff --git a/pdftoebm.py b/pdftoebm.py new file mode 100644 index 0000000..a3785de --- /dev/null +++ b/pdftoebm.py @@ -0,0 +1,92 @@ +""" +Converts PDFs to bitmaps for e-paper displays using Poppler and ImageMagick. + +Usage: python -m doctoebm -o [path] [document] + o: output directory + document: path to PDF +""" +import io +import os +import re +import sys +import getopt +import subprocess +from pathlib import Path + +dpi = "300" +screen_size = "480x800!" + +doc = sys.argv[-1] +argv = sys.argv[1:-1] +opts, args = getopt.getopt(argv, "o:") + +output_path = "." +for o, a in opts: + if o == "-o": + output_path = a.rstrip("/") + if not os.path.exists(output_path): + os.makedirs(output_path) + +root = output_path + "/doc" +print("Converting PDF to JPEGs...") +subprocess.run(["pdftoppm", "-jpeg", "-progress", "-r", dpi, "-thinlinemode", "solid", doc, root]) +print("Finished converting PDF to JPEGs.") + +paths = list(Path(output_path).glob('*.jpg')) + +print("Determining page size...") +w = h = 0 +dx = dy = sys.maxsize +for p in paths: + rv = subprocess.run( + ["magick", p, "-trim", "-format", "%[fx:w] %[fx:h] %[fx:page.x] %[fx:page.y]", "info:"], + capture_output=True, + text=True + ) + area = [int(x) for x in rv.stdout.split()] + if w < area[0]: + w = area[0] + if h < area[1]: + h = area[1] + if dx > area[2]: + dx = area[2] + if dy > area[3]: + dy = area[3] + +crop = "{}x{}+{}+{}".format(w, h, dx, dy) +print("Crop area: {}".format(crop)) + +for i, p in enumerate(paths): + print("Processing page {}/{}...".format(i+1, len(paths))) + jpg = str(p) + txt = jpg.replace(".jpg", ".txt") + ebm = jpg.replace(".jpg", ".ebm") + + subprocess.run(["magick", jpg, "-crop", crop, jpg]) + subprocess.run(["convert", jpg, "-resize", screen_size, jpg]) + subprocess.run(["convert", jpg, "-threshold", "80%", jpg]) + subprocess.run(["mogrify", "-rotate", "-90", jpg]) + subprocess.run(["convert", jpg, "-depth", "1", "-format", "'txt'", txt]) + + with open(txt, "r") as src, open(ebm, "wb") as dst: + total = 0 + n = 7 + x = 0xFF + count = 0 + src.readline() + for line in src: + px = re.search("\([^\)]+\)", line).group() + if px == "(0)": + x &= ~(1 << n) + n -= 1 + if n < 0: + dst.write(x.to_bytes(1)) + count += 1 + total += 1 + if count >= 12: + count = 0 + n = 7 + x = 0xFF + os.remove(txt) + os.remove(jpg) + diff --git a/pdftool.py b/pdftool.py deleted file mode 100644 index 48500d5..0000000 --- a/pdftool.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -Converts PDF documents to bitmaps and generates a C header file -that can be used to render PDFs on Waveshare E-paper displays. This script uses -Poppler and ImageMagick to perform the conversions. - -Usage: python -m pdftool [document] [first page] [last page] [crop page] - document: path to PDF document - first page: start conversion at this page - last page: stop conversion at this page (inclusive) - crop page: an optional page number to use when determining area to crop. - If not specified, each page is croppped to minimize white borders. -""" - -import os -import re -import sys -import subprocess - -pdf = sys.argv[1] -p1 = int(sys.argv[2]) -pn = int(sys.argv[3]) - -# if 4th argument is supplied, crop based on that page. Otherwise maximize per page. -p0 = 0 -crop_area = None -if len(sys.argv) == 5: - p0 = int(sys.argv[4]) - p0_name = "test.jpg" - subprocess.run(["pdftoppm", "-jpeg", "-r", "300", "-thinlinemode", "solid", "-f", str(p0), "-singlefile", pdf, "test"]) - rv = subprocess.run( - ["magick", p0_name, "-trim", "-format", "%[fx:w]x%[fx:h]+%[fx:page.x]+%[fx:page.y]", "info:"], - capture_output=True, - text=True - ) - crop_area = rv.stdout - os.remove(p0_name) - -root = "doc" -jpg = root + ".jpg" -h = root + ".h" -txt = root + ".txt" -macro = root.upper() + "_H" -data_len = pn - p1 + 1 - -with open(h, "w") as f: - f.write("#ifndef {}\n".format(macro)) - f.write("#define {}\n\n".format(macro)) - f.write("const size_t data_len = {};\n\n".format(data_len)) - f.write("const unsigned char data[{0}][48000] = {{\n".format(data_len)) - for p in range(p1, pn + 1): - print("Processing page {}/{}...".format(p, pn)) - subprocess.run(["pdftoppm", "-jpeg", "-r", "300", "-thinlinemode", "solid", "-f", str(p), "-singlefile", pdf, root]) - if crop_area is not None: - subprocess.run(["magick", jpg, "-crop", crop_area, jpg]) - else: - subprocess.run(["mogrify", "-trim", jpg]) - subprocess.run(["convert", jpg, "-resize", "480x800!", jpg]) - subprocess.run(["convert", jpg, "-threshold", "80%", jpg]) - subprocess.run(["mogrify", "-rotate", "-90", jpg]) - subprocess.run(["convert", jpg, "-depth", "1", "-format", "'txt'", txt]) - - f.write("\t{\n\t\t") - total = 0 - with open(txt, "r") as fd: - n = 7 - x = 0xFF - count = 0 - fd.readline() - for line in fd: - px = re.search("\([^\)]+\)", line).group() - if px == "(0)": - x &= ~(1 << n) - n -= 1 - if n < 0: - f.write("0x{:02X}, ".format(x)) - count += 1 - total += 1 - if count >= 12: - f.write("\n\t\t") - count = 0 - x = 0xFF - n = 7 - f.write("\n\t},\n") - f.write("};\n\n") - f.write("#endif /* {} */".format(macro)) - -size = os.path.getsize(h) -if size < 1024: - print("Done! Wrote {:0.1f}B to {}".format(size, h)) -elif size < pow(1024,2): - print("Done! Wrote {:0.1f}KB to {}".format(round(size / 1024, 2), h)) -elif size < pow(1024,3): - print("Done! Wrote {:0.1f}MB to {}".format(round(size / (pow(1024, 2)), 2), h)) -elif size < pow(1024,4): - print("Done! Wrote {:0.1f}GB to {}".format(round(size / (pow(1024, 3)), 2), h)) - -os.remove(txt) -os.remove(jpg) -- cgit v1.2.3