// SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest #include #include #include #include #include #include "BUDisplayMessage.hpp" #include "ftxui/component/component.hpp" #include "ftxui/component/screen_interactive.hpp" #include "ftxui/dom/table.hpp" #include using namespace ftxui; namespace { constexpr std::array MessagePriorityEnumValues = { types::display_message::MessagePriorityEnum::AlwaysFront, types::display_message::MessagePriorityEnum::InFront, types::display_message::MessagePriorityEnum::NormalCycle}; constexpr std::array MessageStateEnumValues = { types::display_message::MessageStateEnum::Charging, types::display_message::MessageStateEnum::Faulted, types::display_message::MessageStateEnum::Idle, types::display_message::MessageStateEnum::Unavailable}; constexpr std::array MessageFormatValues = { types::text_message::MessageFormat::ASCII, types::text_message::MessageFormat::HTML, types::text_message::MessageFormat::URI, types::text_message::MessageFormat::UTF8}; constexpr std::array Identifier_typeValues = { types::display_message::IdentifierType::IdToken, types::display_message::IdentifierType::SessionId, types::display_message::IdentifierType::TransactionId}; // Enum-String to be used for Dropdown GUI elements std::vector MessagePriorityEnumStrings; std::vector MessageStateEnumStrings; std::vector MessageFormatStrings; std::vector IdentifierTypeStrings; struct MessageContentParameters { std::string content; std::string language; std::string msg_id; std::string timestamp_from; std::string timestamp_to; std::string identifier_id; std::string qr_code; int priority; int state; int format; int id_type; }; types::display_message::DisplayMessage inputs2dspmsg_types(const MessageContentParameters& params) { int set_msg_id = 0; if (not params.msg_id.empty()) { set_msg_id = std::stoi(params.msg_id); } types::text_message::MessageContent msg_content{ params.content, params.format == MessageFormatValues.size() ? std::nullopt : std::optional(MessageFormatValues[params.format]), params.language.empty() ? std::nullopt : std::optional{params.language}}; types::display_message::DisplayMessage msg{ msg_content, params.msg_id.empty() ? std::nullopt : std::optional(set_msg_id), params.priority == MessagePriorityEnumValues.size() ? std::nullopt : std::optional(MessagePriorityEnumValues[params.priority]), params.state == MessageStateEnumValues.size() ? std::nullopt : std::optional(MessageStateEnumValues[params.state]), params.timestamp_from.empty() ? std::nullopt : std::optional(params.timestamp_from), params.timestamp_to.empty() ? std::nullopt : std::optional(params.timestamp_to), params.identifier_id.empty() ? std::nullopt : std::optional(params.identifier_id), params.id_type == Identifier_typeValues.size() ? std::nullopt : std::optional(Identifier_typeValues[params.id_type]), params.qr_code.empty() ? std::nullopt : std::optional(params.qr_code)}; return msg; } auto input_parameters_to_display_messages( const std::vector>& set_msg_parameter_vector) { std::vector display_messages; for (auto& msg_params : set_msg_parameter_vector) { if (msg_params->content.empty()) { continue; } display_messages.push_back(inputs2dspmsg_types(*msg_params)); } return display_messages; } auto comma_separated_ids_to_vector(const std::string& get_msg_id_str) { std::optional> get_msg_ids = std::vector{}; int get_msg_id; std::stringstream msg_ids_ss(get_msg_id_str); while (msg_ids_ss.good()) { std::string singleNumber_str; getline(msg_ids_ss, singleNumber_str, ','); try { int singleNumber = std::stoi(singleNumber_str); get_msg_ids->push_back(singleNumber); } catch (...) { // just ignore it } } if (get_msg_ids->size() == 0) { get_msg_ids = std::nullopt; } return get_msg_ids; } auto msg_priority_int_to_optional_enum(int get_msg_priority_selected) { return (get_msg_priority_selected < MessagePriorityEnumValues.size()) ? std::optional< types::display_message::MessagePriorityEnum>{MessagePriorityEnumValues[get_msg_priority_selected]} : std::nullopt; } auto msg_state_int_to_optional_enum(int get_msg_state_selected) { return (get_msg_state_selected < MessageStateEnumValues.size()) ? std::optional{MessageStateEnumValues[get_msg_state_selected]} : std::nullopt; } void log_cmd_response(std::vector* log, const std::string& log_prefix, const std::string& status, const std::optional& status_info) { log->insert(log->begin(), log_prefix + ": " + status + ": '" + status_info.value_or("") + "'"); } void log_message(std::vector* log, types::display_message::DisplayMessage msg) { std::string message_str = msg.message.content; std::string format_str = (msg.message.format ? message_format_to_string(msg.message.format.value()) : ""); std::string lang_str = msg.message.language.value_or(""); std::string id_str = (msg.id ? std::to_string(msg.id.value()) : ""); std::string prio_str = (msg.priority ? message_priority_enum_to_string(msg.priority.value()) : ""); std::string state_str = (msg.state ? message_state_enum_to_string(msg.state.value()) : ""); std::string tstmp_fr_str = msg.timestamp_from.value_or(""); std::string tstmp_to_str = msg.timestamp_to.value_or(""); std::string id_id_str = msg.identifier_id.value_or(""); std::string id_type_str = (msg.identifier_type ? identifier_type_to_string(msg.identifier_type.value()) : ""); std::string qr_str = msg.qr_code.value_or(""); std::vector strings_to_join = {message_str, format_str, lang_str, id_str, prio_str, state_str, tstmp_fr_str, tstmp_to_str, id_id_str, id_type_str, qr_str}; std::string joined_string = std::accumulate( std::begin(strings_to_join), std::end(strings_to_join), std::string{}, [](std::string& first, std::string& second) { return first.empty() ? second : first + ", " + second; }); log->insert(log->begin(), joined_string); } class MessageInputMask : public ComponentBase { public: MessageInputMask(const std::string& heading, MessageContentParameters* parameters) : m_heading(heading), m_parameters(parameters) { m_msg_prio_dd = Dropdown(&MessagePriorityEnumStrings, &(m_parameters->priority)); m_msg_stat_dd = Dropdown(&MessageStateEnumStrings, &(m_parameters->state)); m_msg_form_dd = Dropdown(&MessageFormatStrings, &(m_parameters->format)); m_id_type_dd = Dropdown(&IdentifierTypeStrings, &(m_parameters->id_type)); InputOption id_input_o; id_input_o.multiline = false; id_input_o.cursor_position = 0; id_input_o.on_change = [&]() { if (m_parameters->identifier_id.size() > 36) { m_parameters->identifier_id.resize(36); } }; InputOption msg_id_input_o; msg_id_input_o.multiline = false; msg_id_input_o.cursor_position = 0; msg_id_input_o.on_change = [&]() { m_parameters->msg_id.erase(std::remove_if(m_parameters->msg_id.begin(), m_parameters->msg_id.end(), [](char c) { return !std::isdigit(c); }), m_parameters->msg_id.end()); }; InputOption input_o; input_o.multiline = false; m_msg_input = Input(&(m_parameters->content), "", input_o); m_msg_id_input = Input(&(m_parameters->msg_id), "", msg_id_input_o); m_qr_input = Input(&(m_parameters->qr_code), "", input_o); m_lang_input = Input(&(m_parameters->language), "", input_o); m_t_from_input = Input(&(m_parameters->timestamp_from), "", input_o); m_t_to_input = Input(&(m_parameters->timestamp_to), "", input_o); m_id_input = Input(&(m_parameters->identifier_id), "", id_input_o); m_msg_details_component = Container::Vertical({ m_msg_prio_dd, m_msg_stat_dd, m_id_type_dd, m_msg_form_dd, m_msg_id_input, m_lang_input, m_id_input, m_t_from_input, m_t_to_input, m_qr_input, }); auto set_parameters_renderer = Renderer(m_msg_details_component, [&] { return vbox({ hbox(hbox(text(" [Priority] : ") | vcenter, m_msg_prio_dd->Render()), filler(), hbox(text(" [State] : ") | vcenter, m_msg_stat_dd->Render()), filler()), hbox(hbox(text(" [Identifier Type] : ") | vcenter, m_id_type_dd->Render()), filler(), hbox(text(" [Format] : ") | vcenter, m_msg_form_dd->Render()), filler()), hbox(text(" [Message Id] : "), m_msg_id_input->Render()), hbox(text(" [Language] : "), m_lang_input->Render()), hbox(text(" [Identifier] : "), m_id_input->Render()), hbox(text(" [Date-Time From]: "), m_t_from_input->Render()), hbox(text(" [Date-Time To] : "), m_t_to_input->Render()), hbox(text(" [QR-Code] : "), m_qr_input->Render()), }); }); m_details_collapsible = Collapsible("Message Details", set_parameters_renderer); auto set_msg_component = Container::Vertical({ m_msg_input, m_details_collapsible, }); // Add it to the container so it can receive focus. Add(set_msg_component); } // Override the Render method to display label and input together. Element OnRender() override { return vbox({ text(m_heading) | inverted, hbox(text(" Message : ") | bold, m_msg_input->Render()), m_details_collapsible->Render(), text(""), }); } private: std::string m_heading; MessageContentParameters* m_parameters; Component m_msg_prio_dd; Component m_msg_form_dd; Component m_id_type_dd; Component m_msg_stat_dd; Component m_msg_input; Component m_msg_id_input; Component m_qr_input; Component m_lang_input; Component m_t_from_input; Component m_t_to_input; Component m_id_input; Component m_msg_details_component; Component m_details_collapsible; }; Component MessageInputMaskComponent(const std::string& heading, MessageContentParameters* parameters) { return Make(heading, parameters); } } // namespace namespace module { void BUDisplayMessage::init() { } void BUDisplayMessage::ready() { // Prepare Enum-String to be used for Dropdown GUI elements for (auto msgPriority : MessagePriorityEnumValues) { MessagePriorityEnumStrings.push_back(message_priority_enum_to_string(msgPriority)); } MessagePriorityEnumStrings.push_back(""); for (auto msgState : MessageStateEnumValues) { MessageStateEnumStrings.push_back(message_state_enum_to_string(msgState)); } MessageStateEnumStrings.push_back(""); for (auto msgFormat : MessageFormatValues) { MessageFormatStrings.push_back(message_format_to_string(msgFormat)); } MessageFormatStrings.push_back(""); for (auto idType : Identifier_typeValues) { IdentifierTypeStrings.push_back(identifier_type_to_string(idType)); } IdentifierTypeStrings.push_back(""); /***************************************************************************************+ * Start of FTXUI-GUI ****************************************************************************************/ auto screen = ScreenInteractive::Fullscreen(); int log_pane_index = 0; std::vector log; auto log_pane_raw_component = Menu(&log, &log_pane_index); auto log_pane_component = log_pane_raw_component | vscroll_indicator; auto log_pane_renderer = Renderer(log_pane_component, [&] { return window(text(" Command Log "), log_pane_component->Render()); }); /***************************************************************************************+ * Set Display Messages ****************************************************************************************/ /* ********************** INPUTS ******************************************** */ std::vector> set_msg_parameter_vector{}; int message_input_mask_count; std::vector set_msg_input_masks{}; auto msg_inputs_component = Container::Vertical({}); auto add_msg_input_widget = [&] { set_msg_parameter_vector.push_back(std::make_unique()); // Initialize dropdown choices to be for all dropdowns set_msg_parameter_vector.back()->priority = MessagePriorityEnumStrings.size() - 1; set_msg_parameter_vector.back()->state = MessageStateEnumStrings.size() - 1; set_msg_parameter_vector.back()->format = MessageFormatStrings.size() - 1; set_msg_parameter_vector.back()->id_type = IdentifierTypeStrings.size() - 1; message_input_mask_count += 1; set_msg_input_masks.push_back(MessageInputMaskComponent("Message " + std::to_string(message_input_mask_count), set_msg_parameter_vector.back().get())); msg_inputs_component->Add(set_msg_input_masks.back()); // Tell FTXUI to redraw screen.PostEvent(Event::Custom); }; auto reset_msg_input_widgets = [&] { msg_inputs_component->DetachAllChildren(); set_msg_input_masks.clear(); set_msg_parameter_vector.clear(); message_input_mask_count = 0; add_msg_input_widget(); }; add_msg_input_widget(); /* ********************** BUTTONS ******************************************* */ auto add_msg_button = Button( "Add Message!", [&] { add_msg_input_widget(); }, ButtonOption::Border()); auto set_button = Button( "Set!", [&] { auto display_messages = input_parameters_to_display_messages(set_msg_parameter_vector); if (display_messages.empty()) { return; } auto [status, status_info] = r_dm->call_set_display_message(display_messages); std::string log_prefix{"Set " + std::to_string(display_messages.size()) + " message(s)"}; log_cmd_response(&log, log_prefix, display_message_status_enum_to_string(status), status_info); reset_msg_input_widgets(); }, ButtonOption::Animated(Color::Blue, Color::White, Color::BlueLight, Color::White)); /* ********************** COMPOSITION ******************************************** */ auto set_parameters_renderer = Renderer(msg_inputs_component, [&] { Elements elements; for (auto& input_mask : set_msg_input_masks) { elements.push_back(input_mask->Render()); } return vbox(std::move(elements)); }); auto set_component = Container::Vertical({set_parameters_renderer, add_msg_button, set_button}); /***************************************************************************************+ * Get Display Messages ****************************************************************************************/ /* ********************** INPUTS ******************************************** */ std::string get_msg_id_str; InputOption get_msg_id_input_o; get_msg_id_input_o.multiline = false; get_msg_id_input_o.cursor_position = 0; get_msg_id_input_o.on_change = [&]() { get_msg_id_str.erase(std::remove_if(get_msg_id_str.begin(), get_msg_id_str.end(), [](char c) { return !std::isdigit(c) and c != ','; }), get_msg_id_str.end()); }; auto get_msg_id_input = Input(&get_msg_id_str, "", get_msg_id_input_o); /* ********************** DROPDOWNS ******************************************** */ int get_msg_priority_selected = MessagePriorityEnumStrings.size() - 1; int get_msg_state_selected = MessageStateEnumStrings.size() - 1; auto get_msg_priority_dropdown = Dropdown(&MessagePriorityEnumStrings, &get_msg_priority_selected); auto get_msg_state_dropdown = Dropdown(&MessageStateEnumStrings, &get_msg_state_selected); /* ********************** BUTTON ******************************************** */ auto get_button = Button( "Get!", [&] { auto get_msg_ids = comma_separated_ids_to_vector(get_msg_id_str); auto msgPriorityChoice = msg_priority_int_to_optional_enum(get_msg_priority_selected); auto msgStateChoice = msg_state_int_to_optional_enum(get_msg_state_selected); auto [status_info, messages] = r_dm->call_get_display_messages({get_msg_ids, msgPriorityChoice, msgStateChoice}); std::string log_prefix{"Got " + (messages ? std::to_string(messages->size()) : "no") + " message(s)"}; log_cmd_response(&log, log_prefix, "", status_info); for (auto msg : messages.value_or(std::vector())) { log_message(&log, msg); } }, ButtonOption::Animated(Color::Blue, Color::White, Color::BlueLight, Color::White)); /* ********************** COMPOSITION ******************************************** */ auto get_dropdowns_component = Container::Horizontal({get_msg_priority_dropdown, get_msg_state_dropdown}); auto get_parameters_component = Container::Vertical({ get_dropdowns_component, get_msg_id_input, }); auto get_parameters_renderer = Renderer(get_parameters_component, [&] { return vbox({ text(" Command Parameters ") | inverted, hbox(hbox(text(" [Priority] : ") | vcenter, get_msg_priority_dropdown->Render()), filler(), hbox(text(" [State] : ") | vcenter, get_msg_state_dropdown->Render()), filler()), hbox(text(" [Message Id] : "), get_msg_id_input->Render()), text(" "), }); }); auto get_component = Container::Vertical({get_parameters_renderer, get_button}); /***************************************************************************************+ * Clear Display Messages ****************************************************************************************/ /* ********************** INPUTS ******************************************** */ std::string clear_msg_id_str; InputOption o; o.multiline = false; o.cursor_position = 0; o.on_change = [&]() { clear_msg_id_str.erase( std::remove_if(clear_msg_id_str.begin(), clear_msg_id_str.end(), [](char c) { return !std::isdigit(c); }), clear_msg_id_str.end()); }; auto clear_id_input = Input(&clear_msg_id_str, "", o); /* ********************** BUTTON ******************************************** */ auto clear_button = Button( "Clear!", [&] { int clear_msg_id; try { clear_msg_id = std::stoi(clear_msg_id_str); clear_msg_id_str = std::to_string(clear_msg_id); } catch (...) { return; } auto [status, status_info] = r_dm->call_clear_display_message({clear_msg_id}); std::string log_prefix{"Cleared Messages with ID " + std::to_string(clear_msg_id)}; log_cmd_response(&log, log_prefix, clear_message_response_enum_to_string(status), status_info); }, ButtonOption::Animated(Color::Blue, Color::White, Color::BlueLight, Color::White)); /* ********************** COMPOSITION ******************************************** */ auto clear_parameters_renderer = Renderer(clear_id_input, [&] { return vbox({ text(" Command Parameters ") | inverted, hbox(text(" Message Id : ") | bold, clear_id_input->Render()), text(" "), }); }); auto clear_component = Container::Vertical({clear_parameters_renderer, clear_button}); /***************************************************************************************+ * Composition of full display ****************************************************************************************/ std::vector tab_values{ "Set Message", "Get Message", "Clear Message", }; int tab_selected = 0; auto tab_toggle_component = Toggle(&tab_values, &tab_selected); auto tab_toggle_renderer = Renderer(tab_toggle_component, [&] { return window(text(" Commands "), tab_toggle_component->Render()); }); auto tab_container_component = Container::Tab( { set_component, get_component, clear_component, }, &tab_selected); auto main_container = Container::Vertical({ tab_toggle_renderer, tab_container_component, log_pane_renderer, }); auto main_renderer = Renderer(main_container, [&] { return vbox({ text("Display Message") | bold | hcenter, main_container->Render(), }); }); screen.Loop(main_renderer); } } // namespace module