diff --git a/src/Backend.hpp b/src/Backend.hpp index 9c48871..441ae9d 100644 --- a/src/Backend.hpp +++ b/src/Backend.hpp @@ -57,4 +57,4 @@ class HABackend : Backend std::mutex domainslock; }; -#endif \ No newline at end of file +#endif diff --git a/src/HAEntity.hpp b/src/HAEntity.hpp index 86d9d0d..cc770fa 100644 --- a/src/HAEntity.hpp +++ b/src/HAEntity.hpp @@ -58,6 +58,8 @@ class HAEntity : public ISubject string domain; string fullname; string id; + string platform; + string translation_key; // HAEntity(json _state); HAEntity(json _state, std::shared_ptr _hadomain, HABackend* _backend); diff --git a/src/front-lvgl.cpp b/src/front-lvgl.cpp index 03de543..b5030a1 100644 --- a/src/front-lvgl.cpp +++ b/src/front-lvgl.cpp @@ -37,6 +37,8 @@ namespace lvgl lv_style_t b612style; lv_font_t* mdifont; lv_style_t mdistyle; + map iconmap; // will need a lock eventually + json iconcomponentmap; // lock? } } @@ -82,11 +84,13 @@ void renderCard(std::vector>& uielements, nlohmann::ba auto objs = card["entities"]; for (auto ent : objs) { string entityname; + string icon; if (ent.type() == json::value_t::string) { entityname = ent; } else { entityname = ent["entity"]; + icon = ent.value("icon", ""); } std::shared_ptr entity = HABackend::getInstance().getEntityByName(entityname); if (entity->getEntityType() == EntityType::Light) { @@ -98,7 +102,7 @@ void renderCard(std::vector>& uielements, nlohmann::ba uielements.push_back(std::move(btn)); } else if (entity->getEntityType() == EntityType::Sensor) { - std::unique_ptr sensor = std::make_unique(entity, cont_row); + std::unique_ptr sensor = std::make_unique(entity, cont_row, icon); uielements.push_back(std::move(sensor)); } else { @@ -243,7 +247,7 @@ void uithread(int _argc, char* _argv[]) lv_style_init(&voorkant::lvgl::b612style); lv_style_set_text_font(&voorkant::lvgl::b612style, voorkant::lvgl::b612font); - voorkant::lvgl::mdifont = lv_tiny_ttf_create_data_ex(mdi_ttf, mdi_ttf_len, 16, LV_FONT_KERNING_NORMAL, 1024); + voorkant::lvgl::mdifont = lv_tiny_ttf_create_data_ex(mdi_ttf, mdi_ttf_len, 30, LV_FONT_KERNING_NORMAL, 1024); lv_style_init(&voorkant::lvgl::mdistyle); lv_style_set_text_font(&voorkant::lvgl::mdistyle, voorkant::lvgl::mdifont); @@ -299,6 +303,63 @@ void uithread(int _argc, char* _argv[]) lv_log_register_print_cb(lvLogCallback); std::vector> uielements; + if (program.is_subcommand_used(entity_command) || program.is_subcommand_used(dashboard_command)) { + // need to collect some data used in both cases + + auto& ha = HABackend::getInstance(); + + // FIXME: -cli, -ftxui don't need list_for_display or get_icons + json list_for_display = ha.doCommand("config/entity_registry/list_for_display", {}); + + cerr << list_for_display.dump(2) << endl; + std::set integrations; + for (auto ent : list_for_display["result"]["entities"]) { + auto entity_id = ent["ei"].get(); + auto platform = ent.value("pl", ""); + auto translation_key = ent.value("tk", ""); + integrations.insert(platform); + + auto entity = ha.getEntityByName(entity_id); + entity->platform = platform; + entity->translation_key = translation_key; + } + json get_icons_req; + get_icons_req["category"] = "entity"; // wonder what other categories there are. platform? integration? need to find frank somewhere + + json get_icons = ha.doCommand("frontend/get_icons", get_icons_req); + // inside "resources": + // "sun": { + // "sensor": { + // "next_dawn": { + // "default": "mdi:sun-clock" + // }, + // "next_dusk": { + // "default": "mdi:sun-clock" + // }, + + cerr << get_icons.dump(2) << endl; + + for (auto [platform, data] : get_icons["result"]["resources"].items()) { + // FIXME: this if skips a lot + if (data.count("sensor")) { + for (auto& [key, data2] : data["sensor"].items()) { + // key: next_dawn, data2: "default": " ... "" + auto icon = data2.value("default", ""); + if (!icon.empty()) { + // platform: sun, key: next_dawn, icon: mdi-sun-clock + voorkant::lvgl::iconmap[{platform, key}] = icon; + cerr << "set iconmap[" << platform << "," << key << "]=" << icon << endl; + } + } + } + } + + json get_icons_component_req; + get_icons_component_req["category"] = "entity_component"; + + voorkant::lvgl::iconcomponentmap = ha.doCommand("frontend/get_icons", get_icons_component_req)["result"]["resources"]; + } + if (program.is_subcommand_used(entity_command)) { // FIXME: does this actually need unique_ptr? I guess it might save some copying diff --git a/src/uicomponents/UIComponents.cpp b/src/uicomponents/UIComponents.cpp index 7fd902b..068d1d8 100644 --- a/src/uicomponents/UIComponents.cpp +++ b/src/uicomponents/UIComponents.cpp @@ -1,7 +1,10 @@ #include "UIComponents.hpp" +#include "mdimap.hpp" #include "logger.hpp" #include +#include #include +#include lv_obj_t* UIComponent::createLabel(lv_obj_t* _parent, std::string _text) { @@ -178,7 +181,48 @@ void UIDummy::update() } }; -UISensor::UISensor(std::shared_ptr _entity, lv_obj_t* _parent) : +// if _icon is passed, we got one from the dashboard, use that +string getIconFor(std::shared_ptr _entity, std::string _icon) // for now, this function -always- returns something that starts with mdi: +{ + if (!_icon.empty()) { + return _icon; + } + json state = _entity->getJsonState(); + + // 1. see if the state simply contains an icon - user might have set it explicitly + string icon = state["attributes"].value("icon", ""); + + if (!icon.empty()) { + return icon; + } + + if (state["attributes"].count("entity_picture")) { + // there is an icon, but it is not in a format we support yet (like SVG) + return "mdi:border-none-variant"; + } + // 2. see if we can find one for platform+translation key + voorkant::lvgl::iconkey key = {_entity->platform, _entity->translation_key}; + if (voorkant::lvgl::iconmap.count(key)) { + return voorkant::lvgl::iconmap.at(key); + } + + // 3. maybe we can find one by domain plus device_class? + auto& domain = _entity->domain; + auto device_class = state["attributes"].value("device_class", "_"); + + if (voorkant::lvgl::iconcomponentmap.count(domain)) { + auto& domaindata = voorkant::lvgl::iconcomponentmap[domain]; + if (domaindata.count(device_class)) { + if (domaindata[device_class].count("default")) { // FIXME: handle state-dependent variants too + return domaindata[device_class]["default"]; + } + } + } + + return "mdi:border-none"; +} + +UISensor::UISensor(std::shared_ptr _entity, lv_obj_t* _parent, std::string _icon) : UIEntity(_entity, _parent) { lv_obj_t* flowpanel = lv_obj_create(_parent); @@ -186,16 +230,43 @@ UISensor::UISensor(std::shared_ptr _entity, lv_obj_t* _parent) : lv_obj_set_height(flowpanel, LV_SIZE_CONTENT); lv_obj_set_style_pad_all(flowpanel, 5, LV_PART_MAIN | LV_STATE_DEFAULT); lv_obj_set_align(flowpanel, LV_ALIGN_CENTER); - lv_obj_set_flex_flow(flowpanel, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_flow(flowpanel, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(flowpanel, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START); lv_obj_add_event_cb(flowpanel, UISensor::clickCB, LV_EVENT_CLICKED, reinterpret_cast(&entity)); - lv_obj_t* label = createLabel(flowpanel, entity->name); + lv_obj_t* iconpart = lv_label_create(flowpanel); + lv_obj_set_width(iconpart, 30); + lv_obj_set_height(iconpart, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(iconpart, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(iconpart, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + string icon = getIconFor(_entity, _icon); + // cerr << "iconmap[" << _entity->platform << "," << _entity->translation_key << "]=" << voorkant::lvgl::iconmap[{_entity->platform, _entity->translation_key}] << endl; + + if (icon.substr(0, 4) == "mdi:") { + icon = icon.substr(4); + } + else { + icon = "help"; // as getIconFor currently promises something mdi:, we should never get here + } + + lv_label_set_text(iconpart, voorkant::mdi::name2id(icon).data()); + lv_obj_add_style(iconpart, &voorkant::lvgl::mdistyle, 0); + lv_obj_set_style_text_align(iconpart, LV_TEXT_ALIGN_CENTER, 0); + + lv_obj_t* textpart = lv_obj_create(flowpanel); + lv_obj_set_width(textpart, uiEntityWidth - 55); + lv_obj_set_height(textpart, LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(textpart, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(textpart, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_flex_flow(textpart, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(textpart, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START); + + lv_obj_t* label = createLabel(textpart, entity->name); lv_obj_set_width(label, LV_PCT(100)); lv_obj_set_align(label, LV_ALIGN_LEFT_MID); lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_LEFT, LV_PART_MAIN); - extratext2 = createLabel(flowpanel, "State:"); + extratext2 = createLabel(textpart, "State:"); lv_obj_set_width(extratext2, LV_PCT(100)); lv_obj_set_align(extratext2, LV_ALIGN_RIGHT_MID); lv_obj_set_style_text_align(extratext2, LV_TEXT_ALIGN_RIGHT, LV_PART_MAIN); @@ -203,7 +274,7 @@ UISensor::UISensor(std::shared_ptr _entity, lv_obj_t* _parent) : const auto& services = _entity->getServices(); for (const auto& service : services) { string txt = "Service: "; - lv_obj_t* servicelabel = createLabel(flowpanel, txt.append(service->name)); + lv_obj_t* servicelabel = createLabel(textpart, txt.append(service->name)); lv_obj_set_width(servicelabel, LV_PCT(100)); lv_obj_set_align(servicelabel, LV_ALIGN_LEFT_MID); } diff --git a/src/uicomponents/UIComponents.hpp b/src/uicomponents/UIComponents.hpp index e5e3a10..5a1ae01 100644 --- a/src/uicomponents/UIComponents.hpp +++ b/src/uicomponents/UIComponents.hpp @@ -19,6 +19,9 @@ namespace lvgl extern lv_style_t b612style; extern lv_font_t* mdifont; extern lv_style_t mdistyle; + typedef std::pair iconkey; // platform, translation_key + extern map iconmap; // will need a lock eventually + extern json iconcomponentmap; } } @@ -82,7 +85,7 @@ class UIDummy : public UIEntity class UISensor : public UIEntity { public: - UISensor(std::shared_ptr _entity, lv_obj_t* _parent); + UISensor(std::shared_ptr _entity, lv_obj_t* _parent, std::string _icon = ""); void update() override; private: