diff --git a/meson.build b/meson.build index bcc14990..1cb30c88 100644 --- a/meson.build +++ b/meson.build @@ -198,6 +198,7 @@ if get_option('front-lvgl').enabled() 'src/uicomponents/imgs/colortemp24.c', 'src/uicomponents/imgs/brightness24.c', 'src/uicomponents/imgs/white24.c', + 'src/uicomponents/UICamera.cpp', 'src/uicomponents/uirgblight.cpp', 'src/uicomponents/UILogBox.cpp', 'src/uicomponents/UIComponents.cpp', diff --git a/src/HAEntity.hpp b/src/HAEntity.hpp index cfa695b2..993fb98b 100644 --- a/src/HAEntity.hpp +++ b/src/HAEntity.hpp @@ -21,6 +21,7 @@ enum class EntityType Light, Switch, Fan, + Camera, OTHER, }; diff --git a/src/front-lvgl.cpp b/src/front-lvgl.cpp index c670aec5..4393b176 100644 --- a/src/front-lvgl.cpp +++ b/src/front-lvgl.cpp @@ -191,6 +191,7 @@ void uithread(HABackend& _backend, int _argc, char* _argv[]) {EntityType::Light, makeUIElement}, {EntityType::Switch, makeUIElement}, {EntityType::Fan, makeUIElement}, + {EntityType::Camera, makeUIElement}, {EntityType::OTHER, makeUIElement}}; auto entities = _backend.getEntitiesByPattern(entity_command.get("pattern")); diff --git a/src/front-lvgl.hpp b/src/front-lvgl.hpp index 099393d8..9d69d08d 100644 --- a/src/front-lvgl.hpp +++ b/src/front-lvgl.hpp @@ -24,8 +24,11 @@ #include #include #include "sdl/sdl.h" + +// FIXME the includes for all the components should maybe be in uicomponents.hpp itself? #include "uicomponents/UIComponents.hpp" #include "uicomponents/uirgblight.hpp" +#include "uicomponents/UICamera.hpp" #include using std::string; diff --git a/src/lv_conf.h b/src/lv_conf.h index 37341dec..0c910c5f 100644 --- a/src/lv_conf.h +++ b/src/lv_conf.h @@ -144,7 +144,7 @@ *With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images. *However the opened images might consume additional RAM. *0: to disable caching*/ -#define LV_IMG_CACHE_DEF_SIZE 0 +#define LV_IMG_CACHE_DEF_SIZE 10 /*Number of stops allowed per gradient. Increase this to allow more stops. *This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/ @@ -609,9 +609,9 @@ /*File system interfaces for common APIs */ /*API for fopen, fread, etc*/ -#define LV_USE_FS_STDIO 0 +#define LV_USE_FS_STDIO 1 #if LV_USE_FS_STDIO - #define LV_FS_STDIO_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ + #define LV_FS_STDIO_LETTER 'A' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ #define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/ #define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ #endif @@ -647,14 +647,14 @@ #endif /*PNG decoder library*/ -#define LV_USE_PNG 0 +#define LV_USE_PNG 1 /*BMP decoder library*/ #define LV_USE_BMP 0 /* JPG + split JPG decoder library. * Split JPG is a custom format optimized for embedded systems. */ -#define LV_USE_SJPG 0 +#define LV_USE_SJPG 1 /*GIF decoder library*/ #define LV_USE_GIF 0 diff --git a/src/uicomponents/UICamera.cpp b/src/uicomponents/UICamera.cpp new file mode 100644 index 00000000..d84df848 --- /dev/null +++ b/src/uicomponents/UICamera.cpp @@ -0,0 +1,141 @@ +#include "UICamera.hpp" +#include "logger.hpp" +#include + +// FIXME: we do a whole lot of json parsing in this file, that we should be doing somewhere else. + +UICamera::UICamera(std::shared_ptr _entity, lv_obj_t* _parent) : + UIEntity(_entity, _parent) +{ + auto state = entity->getJsonState(); + std::cerr << "INITIAL STATE FOR " << _entity->name << ":" << state.dump(2) << std::endl; + + if (!state.contains("attributes")) { + throw std::runtime_error("Camera can't operate with a state that has no attributes"); + } + + auto attributes = state["attributes"]; + + // We generate a UI based on 'supported_color_modes'. color_mode then tells us which mode to use. Color_mode should be in update() + imgpanel = lv_img_create(_parent); + lv_img_set_src(imgpanel, "A:test.png"); + // lv_obj_set_width(imgpanel, uiEntityWidth); + // lv_obj_set_height(imgpanel, 380); + lv_obj_set_style_pad_all(imgpanel, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_align(imgpanel, LV_ALIGN_CENTER); + lv_obj_set_flex_flow(imgpanel, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(imgpanel, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START); + + + // lv_obj_align(imgpanel, LV_ALIGN_LEFT_MID, 20, 0); + + update(); +} + +// code from https://curl.se/libcurl/c/getinmemory.html, modified + +// FIXME: use std::array? +struct MemoryStruct { + char *memory; + size_t size; +}; + +static size_t +writeMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + size_t realsize = size * nmemb; + struct MemoryStruct *mem = (struct MemoryStruct *)userp; + + char *ptr = static_cast(realloc(mem->memory, mem->size + realsize + 1)); + if(!ptr) { + /* out of memory! */ + printf("not enough memory (realloc returned NULL)\n"); + return 0; + } + + mem->memory = ptr; + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +// end code from + +void UICamera::update() +{ + auto state = entity->getJsonState(); + std::cerr << "UPDATED STATE FOR " << entity->name << ":" << state.dump(2) << std::endl; + + { + std::unique_lock lvlock(g_lvgl_updatelock); + auto url = "http://grace.7bits.nl:8123" + state["attributes"]["entity_picture"].get(); + g_log << "camera URL: " << url << std::endl; + +// code from as above, modified + CURL *curl_handle; + CURLcode res; + + struct MemoryStruct chunk; + chunk.memory = static_cast(malloc(1)); /* grown as needed by the realloc above */ + chunk.size = 0; /* no data at this point */ + + curl_global_init(CURL_GLOBAL_ALL); + + /* init the curl session */ + curl_handle = curl_easy_init(); + + /* specify URL to get */ + curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str()); + + /* send all data to this function */ + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writeMemoryCallback); + + /* we pass our 'chunk' struct to the callback function */ + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk); + + /* some servers do not like requests that are made without a user-agent + field, so we provide one */ + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); + + /* get it! */ + res = curl_easy_perform(curl_handle); + + /* check for errors */ + if(res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + } + else { + /* + * Now, our chunk.memory points to a memory block that is chunk.size + * bytes big and contains the remote file. + * + * Do something nice with it! + */ + + printf("%lu bytes retrieved\n", (unsigned long)chunk.size); + } + + /* cleanup curl stuff */ + curl_easy_cleanup(curl_handle); +// end code from + + img = { + .header = { + .cf = LV_IMG_CF_RAW_ALPHA, + .always_zero = 0, + .w = 0, + .h = 0, + }, + }; + + img.data_size = chunk.size; + img.data = (uint8_t*)(chunk.memory); + + lv_img_set_src(imgpanel, &img); + + // free(chunk.memory); // FIXME this leaks terribly + } +} diff --git a/src/uicomponents/UICamera.hpp b/src/uicomponents/UICamera.hpp new file mode 100644 index 00000000..17e57d13 --- /dev/null +++ b/src/uicomponents/UICamera.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "UIComponents.hpp" +#include +#include + +class UICamera : public UIEntity +{ +public: + UICamera(std::shared_ptr _entity, lv_obj_t* _parent); + + void update() override; + +private: + // FIXME: we never free() the lv_obj_t*'s in code + lv_obj_t* imgpanel; + lv_img_dsc_t img; +};