Files
Calendink/Provider/main/api/devices/screen_bitmap.cpp
T

236 lines
7.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// GET /api/devices/screen.bin?mac=XX — Render and return a raw 2bpp grayscale
// bitmap for the device's current screen.
// Uses LVGL to render the device's XML layout, quantizes to 4 grayscale levels,
// and packs into 2 bits per pixel (96,000 bytes for 800×480).
#include "esp_heap_caps.h"
#include "esp_http_server.h"
#include "esp_log.h"
#include "lv_setup.hpp"
#include "lvgl.h"
#include <string.h>
#include "device.hpp"
#include "types.hpp"
internal const char *kTagDeviceScreenBitmap = "API_DEV_SCREEN_BMP";
internal esp_err_t api_devices_screen_bitmap_handler(httpd_req_t *req)
{
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_hdr(req, "Cache-Control",
"no-cache, no-store, must-revalidate");
httpd_resp_set_type(req, "application/octet-stream");
// Extract mac query parameter
char mac[18] = {};
size_t buf_len = httpd_req_get_url_query_len(req) + 1;
if (buf_len > 1)
{
char query[64] = {};
if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK)
{
httpd_query_key_value(query, "mac", mac, sizeof(mac));
}
}
if (mac[0] == '\0')
{
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Missing 'mac' query param");
return ESP_FAIL;
}
device_t *dev = find_device(mac);
if (!dev)
{
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Device not registered");
return ESP_FAIL;
}
// --- LVGL rendering (mutex-protected) ---
if (xSemaphoreTake(g_LvglMutex, pdMS_TO_TICKS(5000)) != pdTRUE)
{
ESP_LOGE(kTagDeviceScreenBitmap, "Failed to get LVGL mutex");
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "LVGL busy");
return ESP_FAIL;
}
lv_obj_t *scr = lv_screen_active();
// Clear all children from the active screen
lv_obj_clean(scr);
// White background for grayscale
lv_obj_set_style_bg_color(scr, lv_color_white(), LV_PART_MAIN);
// Setup the MAC address subject so the XML can bind to it
static lv_subject_t mac_subject;
static char mac_buf[18];
static char mac_prev_buf[18];
strncpy(mac_buf, mac, sizeof(mac_buf));
strncpy(mac_prev_buf, mac, sizeof(mac_prev_buf));
lv_subject_init_string(&mac_subject, mac_buf, mac_prev_buf, sizeof(mac_buf),
mac);
// Register the subject in the global XML scope under the name "device_mac"
lv_xml_component_scope_t *global_scope =
lv_xml_component_get_scope("globals");
if (global_scope)
{
lv_xml_register_subject(global_scope, "device_mac", &mac_subject);
ESP_LOGI(kTagDeviceScreenBitmap,
"Registered subject 'device_mac' with value: %s", mac);
}
bool render_success = false;
// 1. Prepare the XML payload
const char *xml_to_register = NULL;
if (dev->xml_layout[0] == '\0')
{
ESP_LOGI(kTagDeviceScreenBitmap, "Device %s has no layout xml.", mac);
xSemaphoreGive(g_LvglMutex);
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "No layout configured");
return ESP_FAIL;
}
if (strstr(dev->xml_layout, "<screen") != NULL)
{
xml_to_register = dev->xml_layout;
ESP_LOGI(kTagDeviceScreenBitmap,
"XML already contains <screen>, passing directly to parser.");
}
// 2. Register the XML payload as a component
lv_result_t res =
lv_xml_register_component_from_data("current_device", xml_to_register);
if (res == LV_RESULT_OK)
{
ESP_LOGI(kTagDeviceScreenBitmap,
"Successfully registered XML for device %s", mac);
lv_obj_t *new_scr = lv_xml_create_screen("current_device");
if (new_scr)
{
lv_screen_load(new_scr);
scr = new_scr;
render_success = true;
}
else
{
ESP_LOGE(kTagDeviceScreenBitmap,
"lv_xml_create_screen failed for device %s", mac);
}
}
else
{
ESP_LOGE(kTagDeviceScreenBitmap,
"lv_xml_register_component_from_data failed for device %s", mac);
}
// 3. Fallback if LVGL XML parsing or creation failed
if (!render_success)
{
ESP_LOGW(kTagDeviceScreenBitmap,
"XML render failed, falling back to raw text layout");
lv_obj_t *label = lv_label_create(scr);
lv_label_set_text(label, "XML Parsing Error\nSee serial log");
lv_obj_set_style_text_color(label, lv_color_black(), LV_PART_MAIN);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}
// Force LVGL to fully render the screen
lv_refr_now(g_LvglDisplay);
lv_draw_buf_t *draw_buf = lv_display_get_buf_active(g_LvglDisplay);
if (!draw_buf)
{
xSemaphoreGive(g_LvglMutex);
ESP_LOGE(kTagDeviceScreenBitmap, "No active draw buffer");
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Display uninitialized");
return ESP_FAIL;
}
uint32_t width = CONFIG_CALENDINK_DISPLAY_WIDTH;
uint32_t height = CONFIG_CALENDINK_DISPLAY_HEIGHT;
// Output: 2 bits per pixel, 4 pixels per byte → width*height/4 bytes
constexpr uint32_t kBitmapSize = 96000; // 800 * 480 / 4
uint8_t *bitmap = (uint8_t *)heap_caps_malloc(kBitmapSize, MALLOC_CAP_SPIRAM);
if (!bitmap)
{
bitmap = (uint8_t *)malloc(kBitmapSize);
if (!bitmap)
{
xSemaphoreGive(g_LvglMutex);
ESP_LOGE(kTagDeviceScreenBitmap, "Failed to allocate bitmap buffer");
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
"Out of memory");
return ESP_FAIL;
}
}
// LVGL renders into RGB565 (2 bytes per pixel).
// Quantize to 4 grayscale levels and pack 4 pixels per byte (2bpp).
// Pixel encoding (MSB first):
// 0b00 = BLACK (lum 0)
// 0b01 = DARK_GRAY (lum 85)
// 0b10 = LIGHT_GRAY (lum 170)
// 0b11 = WHITE (lum 255)
uint32_t bitmap_idx = 0;
for (uint32_t y = 0; y < height; ++y)
{
const uint16_t *src_row =
(const uint16_t *)((const uint8_t *)draw_buf->data +
(y * draw_buf->header.stride));
for (uint32_t x = 0; x < width; x += 4)
{
uint8_t packed = 0;
for (int p = 0; p < 4; ++p)
{
uint16_t c = src_row[x + p];
// Expand 5/6/5 components
uint8_t r_5 = (c >> 11) & 0x1F;
uint8_t g_6 = (c >> 5) & 0x3F;
uint8_t b_5 = c & 0x1F;
// Unpack to 8-bit
uint8_t r = (r_5 << 3) | (r_5 >> 2);
uint8_t g = (g_6 << 2) | (g_6 >> 4);
uint8_t b = (b_5 << 3) | (b_5 >> 2);
// Luminance → 2-bit quantized level (0..3)
uint8_t lum = (r * 77 + g * 150 + b * 29) >> 8;
uint8_t level = lum >> 6; // 0,1,2,3
packed |= (level << (6 - p * 2));
}
bitmap[bitmap_idx++] = packed;
}
}
xSemaphoreGive(g_LvglMutex);
ESP_LOGI(kTagDeviceScreenBitmap, "Bitmap ready: %lu bytes. Sending...",
(unsigned long)bitmap_idx);
esp_err_t sendRes = httpd_resp_send(req, (const char *)bitmap, bitmap_idx);
free(bitmap);
return sendRes;
}
internal const httpd_uri_t api_devices_screen_bitmap_uri = {
.uri = "/api/devices/screen.bin",
.method = HTTP_GET,
.handler = api_devices_screen_bitmap_handler,
.user_ctx = NULL};