Прошло 2 года.

This commit is contained in:
Кобелев Андрей Андреевич
2026-03-10 22:54:23 +03:00
parent c7636ebd6f
commit a111352dc5
313 changed files with 274971 additions and 1409 deletions

View File

@@ -0,0 +1,470 @@
// Note: Run menuconfig to and set the right EPD display and board for your epaper.
// This example does not use a decoded buffer hence leaves more external RAM free
// and it uses a different JPEG decoder: https://github.com/bitbank2/JPEGDEC
// This decoder is not included and should be placed in the components directory
// Check this for a reference on how to do it :
// https://github.com/martinberlin/cale-idf/tree/master/main/www-jpg-render Adding this
// CMakeLists.txt file is required:
/*
set(srcs
"JPEGDEC.cpp"
"jpeg.c"
)
idf_component_register(SRCS ${srcs}
REQUIRES "jpegdec"
INCLUDE_DIRS "include")
*/
// Open settings to set WiFi and other configurations for both examples:
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "esp_task_wdt.h"
#include "esp_timer.h"
#include "esp_types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "settings.h"
// WiFi related
#include "esp_event.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "nvs_flash.h"
// HTTP Client + time
#include "esp_http_client.h"
#include "esp_netif.h"
#include "esp_sntp.h"
// C
#include <math.h> // round + pow
#include <stdio.h>
#include <string.h>
extern "C" {
#include "epd_highlevel.h"
#include "epdiy.h"
}
EpdiyHighlevelState hl;
// JPG decoder from @bitbank2
#include "JPEGDEC.h"
JPEGDEC jpeg;
// EXPERIMENTAL: If JPEG_CPY_FRAMEBUFFER is true the JPG is decoded directly in EPD framebuffer
// On true it looses rotation. Experimental, does not work alright yet. Hint:
// Check if an uint16_t buffer can be copied in a uint8_t buffer directly
#define JPEG_CPY_FRAMEBUFFER true
// Dither space allocation
uint8_t* dither_space;
// Internal array for gamma grayscale
uint8_t gamme_curve[256];
extern "C" {
void app_main();
}
// Load the EMBED_TXTFILES. Then doing (char*) server_cert_pem_start you get the SSL certificate
// Reference:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#embedding-binary-data
extern const uint8_t server_cert_pem_start[] asm("_binary_server_cert_pem_start");
// Buffers
uint8_t* fb; // EPD 2bpp buffer
uint8_t* source_buf; // JPG download buffer
uint32_t buffer_pos = 0;
uint32_t time_download = 0;
uint32_t time_decomp = 0;
uint32_t time_render = 0;
uint16_t ep_width = 0;
uint16_t ep_height = 0;
static const char* TAG = "Jpgdec";
uint16_t countDataEventCalls = 0;
uint32_t countDataBytes = 0;
uint32_t img_buf_pos = 0;
uint64_t startTime = 0;
#if VALIDATE_SSL_CERTIFICATE == true
/* Time aware for ESP32: Important to check SSL certs validity */
void time_sync_notification_cb(struct timeval* tv) {
ESP_LOGI(TAG, "Notification of a time synchronization event");
}
static void initialize_sntp(void) {
ESP_LOGI(TAG, "Initializing SNTP");
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, "pool.ntp.org");
sntp_set_time_sync_notification_cb(time_sync_notification_cb);
#ifdef CONFIG_SNTP_TIME_SYNC_METHOD_SMOOTH
sntp_set_sync_mode(SNTP_SYNC_MODE_SMOOTH);
#endif
sntp_init();
}
static void obtain_time(void) {
initialize_sntp();
// wait for time to be set
time_t now = 0;
struct tm timeinfo = { 0 };
int retry = 0;
const int retry_count = 10;
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) {
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
time(&now);
localtime_r(&now, &timeinfo);
}
#endif
//====================================================================================
// This sketch contains support functions to render the Jpeg images
//
// Created by Bitbank
// Refactored by @martinberlin for EPDiy as a Jpeg download and render example
//====================================================================================
/*
* Used with jpeg.setPixelType(FOUR_BIT_DITHERED)
*/
uint16_t mcu_count = 0;
int JPEGDraw4Bits(JPEGDRAW* pDraw) {
uint32_t render_start = esp_timer_get_time();
#if JPEG_CPY_FRAMEBUFFER
// Highly experimental: Does not support rotation and gamma correction
// Can be washed out compared to JPEG_CPY_FRAMEBUFFER false
for (uint16_t yy = 0; yy < pDraw->iHeight; yy++) {
// Copy directly horizontal MCU pixels in EPD fb
memcpy(
&fb[(pDraw->y + yy) * epd_width() / 2 + pDraw->x / 2],
&pDraw->pPixels[(yy * pDraw->iWidth) >> 2],
pDraw->iWidth
);
}
#else
// Rotation aware
for (int16_t xx = 0; xx < pDraw->iWidth; xx += 4) {
for (int16_t yy = 0; yy < pDraw->iHeight; yy++) {
uint16_t col = pDraw->pPixels[(xx + (yy * pDraw->iWidth)) >> 2];
uint8_t col1 = col & 0xf;
uint8_t col2 = (col >> 4) & 0xf;
uint8_t col3 = (col >> 8) & 0xf;
uint8_t col4 = (col >> 12) & 0xf;
epd_draw_pixel(pDraw->x + xx, pDraw->y + yy, gamme_curve[col1 * 16], fb);
epd_draw_pixel(pDraw->x + xx + 1, pDraw->y + yy, gamme_curve[col2 * 16], fb);
epd_draw_pixel(pDraw->x + xx + 2, pDraw->y + yy, gamme_curve[col3 * 16], fb);
epd_draw_pixel(pDraw->x + xx + 3, pDraw->y + yy, gamme_curve[col4 * 16], fb);
/* if (yy==0 && mcu_count==0) {
printf("1.%d %d %d %d ",col1,col2,col3,col4);
} */
}
}
#endif
mcu_count++;
time_render += (esp_timer_get_time() - render_start) / 1000;
return 1;
}
void deepsleep() {
esp_deep_sleep(1000000LL * 60 * DEEPSLEEP_MINUTES_AFTER_RENDER);
}
//====================================================================================
// This function opens source_buf Jpeg image file and primes the decoder
//====================================================================================
int decodeJpeg(uint8_t* source_buf, int xpos, int ypos) {
uint32_t decode_start = esp_timer_get_time();
if (jpeg.openRAM(source_buf, img_buf_pos, JPEGDraw4Bits)) {
jpeg.setPixelType(FOUR_BIT_DITHERED);
if (jpeg.decodeDither(dither_space, 0)) {
time_decomp = (esp_timer_get_time() - decode_start) / 1000 - time_render;
ESP_LOGI(
"decode",
"%d ms - %dx%d image MCUs:%d",
time_decomp,
jpeg.getWidth(),
jpeg.getHeight(),
mcu_count
);
} else {
ESP_LOGE("jpeg.decode", "Failed with error: %d", jpeg.getLastError());
}
} else {
ESP_LOGE("jpeg.openRAM", "Failed with error: %d", jpeg.getLastError());
}
jpeg.close();
return 1;
}
// Handles Htpp events and is in charge of buffering source_buf (jpg compressed image)
esp_err_t _http_event_handler(esp_http_client_event_t* evt) {
switch (evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGE(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
#if DEBUG_VERBOSE
ESP_LOGI(
TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value
);
#endif
break;
case HTTP_EVENT_ON_DATA:
++countDataEventCalls;
#if DEBUG_VERBOSE
if (countDataEventCalls % 10 == 0) {
ESP_LOGI(TAG, "%d len:%d\n", countDataEventCalls, evt->data_len);
}
#endif
if (countDataEventCalls == 1)
startTime = esp_timer_get_time();
// Append received data into source_buf
memcpy(&source_buf[img_buf_pos], evt->data, evt->data_len);
img_buf_pos += evt->data_len;
break;
case HTTP_EVENT_ON_FINISH:
// Do not draw if it's a redirect (302)
if (esp_http_client_get_status_code(evt->client) == 200) {
printf("%d bytes read from %s\n", img_buf_pos, IMG_URL);
time_download = (esp_timer_get_time() - startTime) / 1000;
decodeJpeg(source_buf, 0, 0);
ESP_LOGI("www-dw", "%d ms - download", time_download);
ESP_LOGI(
"render",
"%d ms - copying pix (JPEG_CPY_FRAMEBUFFER:%d)",
time_render,
JPEG_CPY_FRAMEBUFFER
);
// Refresh display
epd_hl_update_screen(&hl, MODE_GC16, 25);
ESP_LOGI(
"total", "%d ms - total time spent\n", time_download + time_decomp + time_render
);
} else {
printf(
"HTTP on finish got status code: %d\n",
esp_http_client_get_status_code(evt->client)
);
}
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED\n");
break;
}
return ESP_OK;
}
// Handles http request
static void http_post(void) {
/**
* NOTE: All the configuration parameters for http_client must be specified
* either in URL or as host and path parameters.
*/
esp_http_client_config_t config = { .url = IMG_URL,
#if VALIDATE_SSL_CERTIFICATE == true
.cert_pem = (char*)server_cert_pem_start,
#endif
.disable_auto_redirect = false,
.event_handler = _http_event_handler,
.buffer_size = HTTP_RECEIVE_BUFFER_SIZE };
esp_http_client_handle_t client = esp_http_client_init(&config);
#if DEBUG_VERBOSE
printf("Free heap before HTTP download: %d\n", xPortGetFreeHeapSize());
if (esp_http_client_get_transport_type(client) == HTTP_TRANSPORT_OVER_SSL && config.cert_pem) {
printf("SSL CERT:\n%s\n\n", (char*)server_cert_pem_start);
}
#endif
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(
TAG,
"\nIMAGE URL: %s\n\nHTTP GET Status = %d, content_length = %d\n",
IMG_URL,
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client)
);
} else {
ESP_LOGE(TAG, "\nHTTP GET request failed: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
#if MILLIS_DELAY_BEFORE_SLEEP > 0
vTaskDelay(MILLIS_DELAY_BEFORE_SLEEP / portTICK_PERIOD_MS);
#endif
printf("Go to sleep %d minutes\n", DEEPSLEEP_MINUTES_AFTER_RENDER);
epd_poweroff();
deepsleep();
}
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
static void event_handler(
void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data
) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < 5) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "Retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
ESP_LOGI(
TAG,
"Connect to the AP failed %d times. Going to deepsleep %d minutes",
5,
DEEPSLEEP_MINUTES_AFTER_RENDER
);
deepsleep();
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
// Initializes WiFi the ESP-IDF way
void wifi_init_sta(void) {
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
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 instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id
));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip
));
// C++ wifi config
wifi_config_t wifi_config;
memset(&wifi_config, 0, sizeof(wifi_config));
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
sprintf(reinterpret_cast<char*>(wifi_config.sta.ssid), ESP_WIFI_SSID);
sprintf(reinterpret_cast<char*>(wifi_config.sta.password), ESP_WIFI_PASSWORD);
wifi_config.sta.pmf_cfg.capable = true;
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config((wifi_interface_t)ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed
* for the maximum number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see
* above) */
EventBits_t bits = xEventGroupWaitBits(
s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY
);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which
* event actually happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Connected to ap SSID:%s", ESP_WIFI_SSID);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s", ESP_WIFI_SSID);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
/* The event will not be processed after unregister */
ESP_ERROR_CHECK(
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip)
);
ESP_ERROR_CHECK(
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id)
);
vEventGroupDelete(s_wifi_event_group);
}
void app_main() {
epd_init(EPD_OPTIONS_DEFAULT);
hl = epd_hl_init(WAVEFORM);
fb = epd_hl_get_framebuffer(&hl);
printf("JPGDEC version @bitbank2\n");
dither_space = (uint8_t*)heap_caps_malloc(epd_width() * 16, MALLOC_CAP_SPIRAM);
if (dither_space == NULL) {
ESP_LOGE("main", "Initial alloc ditherSpace failed!");
}
// Should be big enough to allocate the JPEG file size, width * height should suffice
source_buf = (uint8_t*)heap_caps_malloc(epd_width() * epd_height(), MALLOC_CAP_SPIRAM);
if (source_buf == NULL) {
ESP_LOGE("main", "Initial alloc source_buf failed!");
}
printf("Free heap after buffers allocation: %d\n", xPortGetFreeHeapSize());
double gammaCorrection = 1.0 / gamma_value;
for (int gray_value = 0; gray_value < 256; gray_value++)
gamme_curve[gray_value] = round(255 * pow(gray_value / 255.0, gammaCorrection));
#if JPEG_CPY_FRAMEBUFFER == false
epd_set_rotation(DISPLAY_ROTATION);
#endif
// Initialize NVS
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);
// WiFi log level set only to Error otherwise outputs too much
esp_log_level_set("wifi", ESP_LOG_ERROR);
epd_poweron();
epd_fullclear(&hl, 25);
// Initialization: WiFi + clean screen while downloading image
printf("Free heap before wifi_init_sta: %d\n", xPortGetFreeHeapSize());
wifi_init_sta();
#if VALIDATE_SSL_CERTIFICATE == true
obtain_time();
#endif
http_post();
}