diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 152637ee..6f3c4a0f 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -33,6 +33,9 @@ - Scanning SD card for external apps and auto-register them (in a temporary register?) - Support hot-plugging SD card - All drivers (e.g. display, touch, etc.) should call stop() in their destructor, or at least assert that they should not be running. +- Use GPS time to set/update the current time +- Investigate EEZ Studio +- Remove flex_flow from app_container in Gui.cpp # Nice-to-haves - Give external app a different icon. Allow an external app update their id, icon, type and name once they are running(and persist that info?). Loader will need to be able to find app by (external) location. @@ -66,3 +69,7 @@ - GPS app - Investigate CSI https://stevenmhernandez.github.io/ESP32-CSI-Tool/ - Compile unix tools to ELF apps? +- Calculator +- Text editor +- Todo list +- Calendar diff --git a/Tactility/Include/Tactility/app/alertdialog/AlertDialog.h b/Tactility/Include/Tactility/app/alertdialog/AlertDialog.h index ae689deb..32d439e1 100644 --- a/Tactility/Include/Tactility/app/alertdialog/AlertDialog.h +++ b/Tactility/Include/Tactility/app/alertdialog/AlertDialog.h @@ -21,6 +21,13 @@ namespace tt::app::alertdialog { */ void start(const std::string& title, const std::string& message, const std::vector& buttonLabels); + /** + * Show a dialog with the provided title, message and an OK button + * @param[in] title the title to show in the toolbar + * @param[in] message the message to display + */ + void start(const std::string& title, const std::string& message); + /** * Get the index of the button that the user selected. * diff --git a/Tactility/Include/Tactility/app/serialconsole/ConnectView.h b/Tactility/Include/Tactility/app/serialconsole/ConnectView.h new file mode 100644 index 00000000..3113d8fe --- /dev/null +++ b/Tactility/Include/Tactility/app/serialconsole/ConnectView.h @@ -0,0 +1,143 @@ +#pragma once + +#include "./View.h" + +#include "Tactility/Preferences.h" +#include "Tactility/Tactility.h" +#include "Tactility/app/alertdialog/AlertDialog.h" +#include "Tactility/hal/uart/Uart.h" +#include "Tactility/lvgl/LvglSync.h" +#include "Tactility/lvgl/Style.h" +#include "Tactility/service/gui/Gui.h" + +#include +#include +#include + +namespace tt::app::serialconsole { + +class ConnectView final : public View { + +public: + + typedef std::function)> OnConnectedFunction; + std::vector uartNames; + Preferences preferences = Preferences("SerialConsole"); + +private: + + OnConnectedFunction onConnected; + lv_obj_t* busDropdown = nullptr; + lv_obj_t* speedTextarea = nullptr; + + int32_t getSpeedInput() const { + auto* speed_text = lv_textarea_get_text(speedTextarea); + return std::stoi(speed_text); + } + + void onConnect() { + auto lock = lvgl::getSyncLock()->asScopedLock(); + if (!lock.lock(lvgl::defaultLockTime)) { + return; + } + + auto selected_uart_index = lv_dropdown_get_selected(busDropdown); + if (selected_uart_index >= uartNames.size()) { + alertdialog::start("Error", "No UART selected"); + return; + } + + auto uart_name = uartNames[selected_uart_index]; + auto uart = hal::uart::open(uart_name); + if (uart == nullptr) { + alertdialog::start("Error", "Failed to connect to UART"); + return; + } + + int speed = getSpeedInput(); + if (speed <= 0) { + alertdialog::start("Error", "Invalid speed"); + return; + } + + if (!uart->start()) { + alertdialog::start("Error", "Failed to initialize"); + return; + } + + if (!uart->setBaudRate(speed)) { + uart->stop(); + alertdialog::start("Error", "Failed to set baud rate"); + return; + } + + onConnected(std::move(uart)); + } + + static void onConnectCallback(lv_event_t* event) { + auto* view = (ConnectView*)lv_event_get_user_data(event); + view->onConnect(); + } + +public: + + explicit ConnectView(OnConnectedFunction onConnected) : onConnected(std::move(onConnected)) {} + + void onStart(lv_obj_t* parent) { + uartNames = hal::uart::getNames(); + + auto* wrapper = lv_obj_create(parent); + lv_obj_set_size(wrapper, LV_PCT(100), LV_PCT(100)); + lv_obj_set_style_pad_ver(wrapper, 0, 0); + lv_obj_set_style_border_width(wrapper, 0, 0); + lvgl::obj_set_style_bg_invisible(wrapper); + + busDropdown = lv_dropdown_create(wrapper); + + auto bus_options = string::join(uartNames, "\n"); + lv_dropdown_set_options(busDropdown, bus_options.c_str()); + lv_obj_align(busDropdown, LV_ALIGN_TOP_RIGHT, 0, 0); + lv_obj_set_width(busDropdown, LV_PCT(50)); + int32_t bus_index = 0; + preferences.optInt32("bus", bus_index); + if (bus_index < uartNames.size()) { + lv_dropdown_set_selected(busDropdown, bus_index); + } + + auto* bus_label = lv_label_create(wrapper); + lv_obj_align(bus_label, LV_ALIGN_TOP_LEFT, 0, 10); + lv_label_set_text(bus_label, "Bus"); + + int32_t speed = 115200; + preferences.optInt32("speed", speed); + speedTextarea = lv_textarea_create(wrapper); + lv_textarea_set_text(speedTextarea, std::to_string(speed).c_str()); + lv_textarea_set_one_line(speedTextarea, true); + lv_obj_set_width(speedTextarea, LV_PCT(50)); + lv_obj_align(speedTextarea, LV_ALIGN_TOP_RIGHT, 0, 40); + service::gui::keyboardAddTextArea(speedTextarea); + + auto* baud_rate_label = lv_label_create(wrapper); + lv_obj_align(baud_rate_label, LV_ALIGN_TOP_LEFT, 0, 50); + lv_label_set_text(baud_rate_label, "Baud"); + + auto* connect_button = lv_button_create(wrapper); + auto* connect_label = lv_label_create(connect_button); + lv_label_set_text(connect_label, "Connect"); + lv_obj_align(connect_button, LV_ALIGN_TOP_MID, 0, 90); + lv_obj_add_event_cb(connect_button, onConnectCallback, LV_EVENT_SHORT_CLICKED, this); + } + + void onStop() final { + int speed = getSpeedInput(); + if (speed > 0) { + preferences.putInt32("speed", speed); + } + + auto bus_index = (int32_t)lv_dropdown_get_selected(busDropdown); + preferences.putInt32("bus", bus_index); + } +}; + + +} // namespace tt::app::serialconsole diff --git a/Tactility/Include/Tactility/app/serialconsole/ConsoleView.h b/Tactility/Include/Tactility/app/serialconsole/ConsoleView.h new file mode 100644 index 00000000..9e4cc45f --- /dev/null +++ b/Tactility/Include/Tactility/app/serialconsole/ConsoleView.h @@ -0,0 +1,296 @@ +#pragma once + +#include "./View.h" +#include "Tactility/Timer.h" +#include +#include + +#define TAG "SerialConsole" + +namespace tt::app::serialconsole { + +constexpr size_t receiveBufferSize = 512; +constexpr size_t renderBufferSize = receiveBufferSize + 2; // Leave space for newline at split and null terminator at the end + +class ConsoleView final : public View { + +private: + + lv_obj_t* _Nullable parent = nullptr; + lv_obj_t* _Nullable logTextarea = nullptr; + lv_obj_t* _Nullable inputTextarea = nullptr; + std::unique_ptr _Nullable uart = nullptr; + std::shared_ptr uartThread _Nullable = nullptr; + bool uartThreadInterrupted = false; + std::shared_ptr viewThread _Nullable = nullptr; + bool viewThreadInterrupted = false; + Mutex mutex = Mutex(Mutex::Type::Recursive); + uint8_t receiveBuffer[receiveBufferSize]; + uint8_t renderBuffer[renderBufferSize]; + size_t receiveBufferPosition = 0; + std::string terminatorString = "\n"; + + bool isUartThreadInterrupted() const { + auto lock = mutex.asScopedLock(); + lock.lock(); + return uartThreadInterrupted; + } + + bool isViewThreadInterrupted() const { + auto lock = mutex.asScopedLock(); + lock.lock(); + return viewThreadInterrupted; + } + + void updateViews() { + auto lvgl_lock = lvgl::getSyncLock()->asScopedLock(); + if (!lvgl_lock.lock(lvgl::defaultLockTime)) { + return; + } + + if (parent == nullptr) { + return; + } + + // Updating the view is expensive, so we only want to set the text once: + // Gather all the lines in a single buffer + if (mutex.lock()) { + size_t first_part_size = receiveBufferSize - receiveBufferPosition; + memcpy(renderBuffer, receiveBuffer + receiveBufferPosition, first_part_size); + renderBuffer[receiveBufferPosition] = '\n'; + if (receiveBufferPosition > 0) { + memcpy(renderBuffer + first_part_size + 1, receiveBuffer, (receiveBufferSize - first_part_size)); + renderBuffer[receiveBufferSize - 1] = 0x00; + } + mutex.unlock(); + } + + if (lvgl::lock(lvgl::defaultLockTime)) { + lv_textarea_set_text(logTextarea, (const char*)renderBuffer); + lvgl::unlock(); + } + } + + int32_t viewThreadMain() { + while (!isViewThreadInterrupted()) { + auto start_time = kernel::getTicks(); + + updateViews(); + + auto end_time = kernel::getTicks(); + auto time_diff = end_time - start_time; + if (time_diff < 500U) { + kernel::delayTicks((500U - time_diff) / portTICK_PERIOD_MS); + } + } + + return 0; + } + + int32_t uartThreadMain() { + uint8_t byte; + + while (!isUartThreadInterrupted()) { + assert(uart != nullptr); + bool success = uart->readByte(&byte, 50 / portTICK_PERIOD_MS); + + // Thread might've been interrupted in the meanwhile + if (isUartThreadInterrupted()) { + break; + } + + if (success) { + mutex.lock(); + receiveBuffer[receiveBufferPosition++] = byte; + if (receiveBufferPosition == receiveBufferSize) { + receiveBufferPosition = 0; + } + mutex.unlock(); + } + + } + + return 0; + } + + static int32_t viewThreadMainStatic(void* parameter) { + auto* view = (ConsoleView*)parameter; + return view->viewThreadMain(); + } + + static int32_t uartThreadMainStatic(void* parameter) { + auto* view = (ConsoleView*)parameter; + return view->uartThreadMain(); + } + + static void onSendClickedCallback(lv_event_t* event) { + auto* view = (ConsoleView*)lv_event_get_user_data(event); + view->onSendClicked(); + } + + static void onTerminatorDropdownValueChangedCallback(lv_event_t* event) { + auto* view = (ConsoleView*)lv_event_get_user_data(event); + view->onTerminatorDropDownValueChanged(event); + } + + void onTerminatorDropDownValueChanged(lv_event_t* event) { + auto* dropdown = static_cast(lv_event_get_target(event)); + mutex.lock(); + switch (lv_dropdown_get_selected(dropdown)) { + case 0: + terminatorString = "\n"; + break; + case 1: + terminatorString = "\r\n"; + break; + } + mutex.unlock(); + } + + void onSendClicked() { + mutex.lock(); + std::string input_text = lv_textarea_get_text(inputTextarea); + std::string to_send = input_text + terminatorString; + mutex.unlock(); + + auto* safe_uart = uart.get(); + if (safe_uart != nullptr) { + if (!safe_uart->writeString(to_send.c_str(), 100 / portTICK_PERIOD_MS)) { + TT_LOG_E(TAG, "Failed to send \"%s\"", input_text.c_str()); + } + } + + lv_textarea_set_text(inputTextarea, ""); + } + +public: + + void startLogic(std::unique_ptr newUart) { + memset(receiveBuffer, 0, receiveBufferSize); + + assert(uartThread == nullptr); + assert(uart == nullptr); + + uart = std::move(newUart); + + uartThreadInterrupted = false; + uartThread = std::make_unique( + "SerConsUart", + 4096, + uartThreadMainStatic, + this + ); + uartThread->setPriority(tt::Thread::Priority::High); + uartThread->start(); + } + + void startViews(lv_obj_t* parent) { + this->parent = parent; + + lv_obj_set_style_pad_gap(parent, 2, 0); + + logTextarea = lv_textarea_create(parent); + lv_textarea_set_placeholder_text(logTextarea, "Waiting for data..."); + lv_obj_set_flex_grow(logTextarea, 1); + lv_obj_set_width(logTextarea, LV_PCT(100)); + lv_obj_add_state(logTextarea, LV_STATE_DISABLED); + lv_obj_set_style_margin_ver(logTextarea, 0, 0); + service::gui::keyboardAddTextArea(logTextarea); + + auto* input_wrapper = lv_obj_create(parent); + lv_obj_set_size(input_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(input_wrapper, 0, 0); + lv_obj_set_style_border_width(input_wrapper, 0, 0); + lv_obj_set_width(input_wrapper, LV_PCT(100)); + lv_obj_set_flex_flow(input_wrapper, LV_FLEX_FLOW_ROW); + + inputTextarea = lv_textarea_create(input_wrapper); + lv_textarea_set_one_line(inputTextarea, true); + lv_textarea_set_placeholder_text(inputTextarea, "Text to send"); + lv_obj_set_width(inputTextarea, LV_PCT(100)); + lv_obj_set_flex_grow(inputTextarea, 1); + service::gui::keyboardAddTextArea(inputTextarea); + + auto* terminator_dropdown = lv_dropdown_create(input_wrapper); + lv_dropdown_set_options(terminator_dropdown, "\\n\n\\r\\n"); + lv_obj_set_width(terminator_dropdown, 70); + lv_obj_add_event_cb(terminator_dropdown, onTerminatorDropdownValueChangedCallback, LV_EVENT_VALUE_CHANGED, this); + + + auto* button = lv_button_create(input_wrapper); + auto* button_label = lv_label_create(button); + lv_label_set_text(button_label, "Send"); + lv_obj_add_event_cb(button, onSendClickedCallback, LV_EVENT_SHORT_CLICKED, this); + + viewThreadInterrupted = false; + viewThread = std::make_unique( + "SerConsView", + 4096, + viewThreadMainStatic, + this + ); + viewThread->setPriority(THREAD_PRIORITY_RENDER); + viewThread->start(); + } + + void stopLogic() { + auto lock = mutex.asScopedLock(); + lock.lock(); + + uartThreadInterrupted = true; + + // Detach thread, it will auto-delete when leaving the current scope + auto old_uart_thread = std::move(uartThread); + // Unlock so thread can lock + lock.unlock(); + + if (old_uart_thread->getState() != Thread::State::Stopped) { + // Wait for thread to finish + old_uart_thread->join(); + } + } + + void stopViews() { + auto lock = mutex.asScopedLock(); + lock.lock(); + + viewThreadInterrupted = true; + + // Detach thread, it will auto-delete when leaving the current scope + auto old_view_thread = std::move(viewThread); + + // Unlock so thread can lock + lock.unlock(); + + if (old_view_thread->getState() != Thread::State::Stopped) { + // Wait for thread to finish + old_view_thread->join(); + } + } + + void stopUart() { + auto lock = mutex.asScopedLock(); + lock.lock(); + + if (uart != nullptr && uart->isStarted()) { + uart->stop(); + uart = nullptr; + } + } + + void onStart(lv_obj_t* parent, std::unique_ptr newUart) { + auto lock = mutex.asScopedLock(); + lock.lock(); + + startLogic(std::move(newUart)); + startViews(parent); + } + + void onStop() final { + stopViews(); + stopLogic(); + stopUart(); + } +}; + +} // namespace tt::app::serialconsole diff --git a/Tactility/Include/Tactility/app/serialconsole/View.h b/Tactility/Include/Tactility/app/serialconsole/View.h new file mode 100644 index 00000000..58db8c9c --- /dev/null +++ b/Tactility/Include/Tactility/app/serialconsole/View.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace tt::app::serialconsole { + +class View { +public: + virtual void onStop() = 0; +}; + +} \ No newline at end of file diff --git a/Tactility/Include/Tactility/lvgl/LvglSync.h b/Tactility/Include/Tactility/lvgl/LvglSync.h index 5f4209cc..35a05797 100644 --- a/Tactility/Include/Tactility/lvgl/LvglSync.h +++ b/Tactility/Include/Tactility/lvgl/LvglSync.h @@ -6,6 +6,8 @@ namespace tt::lvgl { +constexpr TickType_t defaultLockTime = 500 / portTICK_PERIOD_MS; + /** * LVGL locking function * @param[in] timeoutMillis timeout in milliseconds. waits forever when 0 is passed. diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 8f7652d8..8a8508ff 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -45,6 +45,7 @@ namespace app { namespace log { extern const AppManifest manifest; } namespace power { extern const AppManifest manifest; } namespace selectiondialog { extern const AppManifest manifest; } + namespace serialconsole { extern const AppManifest manifest; } namespace settings { extern const AppManifest manifest; } namespace systeminfo { extern const AppManifest manifest; } namespace textviewer { extern const AppManifest manifest; } @@ -80,6 +81,7 @@ static void registerSystemApps() { addApp(app::inputdialog::manifest); addApp(app::launcher::manifest); addApp(app::log::manifest); + addApp(app::serialconsole::manifest); addApp(app::settings::manifest); addApp(app::selectiondialog::manifest); addApp(app::systeminfo::manifest); diff --git a/Tactility/Source/app/alertdialog/AlertDialog.cpp b/Tactility/Source/app/alertdialog/AlertDialog.cpp index 979212df..3e2539d2 100644 --- a/Tactility/Source/app/alertdialog/AlertDialog.cpp +++ b/Tactility/Source/app/alertdialog/AlertDialog.cpp @@ -31,6 +31,14 @@ void start(const std::string& title, const std::string& message, const std::vect service::loader::startApp(manifest.id, bundle); } +void start(const std::string& title, const std::string& message) { + auto bundle = std::make_shared(); + bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title); + bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message); + bundle->putString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, "OK"); + service::loader::startApp(manifest.id, bundle); +} + int32_t getResultIndex(const Bundle& bundle) { int32_t index = -1; bundle.optInt32(RESULT_BUNDLE_KEY_INDEX, index); @@ -76,6 +84,7 @@ private: lv_label_set_text(button_label, text.c_str()); lv_obj_add_event_cb(button, onButtonClickedCallback, LV_EVENT_SHORT_CLICKED, (void*)index); } + public: void onShow(AppContext& app, lv_obj_t* parent) override { @@ -89,6 +98,7 @@ public: lv_obj_t* message_label = lv_label_create(parent); lv_obj_align(message_label, LV_ALIGN_CENTER, 0, 0); lv_obj_set_width(message_label, LV_PCT(80)); + lv_obj_set_style_text_align(message_label, LV_TEXT_ALIGN_CENTER, 0); std::string message; if (parameters->optString(PARAMETER_BUNDLE_KEY_MESSAGE, message)) { @@ -107,21 +117,9 @@ public: std::string items_concatenated; if (parameters->optString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, items_concatenated)) { std::vector labels = string::split(items_concatenated, PARAMETER_ITEM_CONCATENATION_TOKEN); - if (labels.empty() || labels.front().empty()) { - TT_LOG_E(TAG, "No items provided"); - setResult(Result::Error); - service::loader::stopApp(); - } else if (labels.size() == 1) { - auto result_bundle = std::make_unique(); - result_bundle->putInt32(RESULT_BUNDLE_KEY_INDEX, 0); - setResult(Result::Ok, std::move(result_bundle)); - service::loader::stopApp(); - TT_LOG_W(TAG, "Auto-selecting single item"); - } else { - size_t index = 0; - for (const auto& label: labels) { - createButton(button_wrapper, label, index++); - } + size_t index = 0; + for (const auto& label: labels) { + createButton(button_wrapper, label, index++); } } } diff --git a/Tactility/Source/app/serialconsole/SerialConsole.cpp b/Tactility/Source/app/serialconsole/SerialConsole.cpp new file mode 100644 index 00000000..41f433cd --- /dev/null +++ b/Tactility/Source/app/serialconsole/SerialConsole.cpp @@ -0,0 +1,96 @@ +#include "Tactility/app/serialconsole/ConnectView.h" +#include "Tactility/app/serialconsole/ConsoleView.h" + +#include "Tactility/lvgl/LvglSync.h" +#include "Tactility/lvgl/Style.h" +#include "Tactility/lvgl/Toolbar.h" +#include "Tactility/service/loader/Loader.h" + +#include + +#include + +#define TAG "text_viewer" + +namespace tt::app::serialconsole { + +class SerialConsoleApp final : public App { + +private: + + lv_obj_t* disconnectButton = nullptr; + lv_obj_t* wrapperWidget = nullptr; + ConnectView connectView = ConnectView([this](auto uart){ + showConsoleView(std::move(uart)); + }); + ConsoleView consoleView; + View* activeView = nullptr; + + void stopActiveView() { + if (activeView != nullptr) { + activeView->onStop(); + lv_obj_clean(wrapperWidget); + activeView = nullptr; + } + } + + void showConsoleView(std::unique_ptr uart) { + stopActiveView(); + activeView = &consoleView; + consoleView.onStart(wrapperWidget, std::move(uart)); + lv_obj_remove_flag(disconnectButton, LV_OBJ_FLAG_HIDDEN); + } + + void showConnectView() { + stopActiveView(); + activeView = &connectView; + connectView.onStart(wrapperWidget); + lv_obj_add_flag(disconnectButton, LV_OBJ_FLAG_HIDDEN); + } + + void onDisconnect() { + // Changing views (calling ConsoleView::stop()) also disconnects the UART + showConnectView(); + } + + static void onDisconnectPressed(lv_event_t* event) { + auto* app = (SerialConsoleApp*)lv_event_get_user_data(event); + app->onDisconnect(); + } + +public: + + SerialConsoleApp() = default; + + void onShow(AppContext& app, lv_obj_t* parent) final { + lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); + auto* toolbar = lvgl::toolbar_create(parent, app); + + disconnectButton = lvgl::toolbar_add_button_action(toolbar, LV_SYMBOL_POWER, onDisconnectPressed, this); + lv_obj_add_flag(disconnectButton, LV_OBJ_FLAG_HIDDEN); + + wrapperWidget = lv_obj_create(parent); + lv_obj_set_width(wrapperWidget, LV_PCT(100)); + lv_obj_set_flex_grow(wrapperWidget, 1); + lv_obj_set_flex_flow(wrapperWidget, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_all(wrapperWidget, 0, 0); + lv_obj_set_style_border_width(wrapperWidget, 0, 0); + lvgl::obj_set_style_bg_invisible(wrapperWidget); + + showConnectView(); + } + + void onHide(AppContext& app) final { + stopActiveView(); + } +}; + +extern const AppManifest manifest = { + .id = "SerialConsole", + .name = "Serial Console", + .icon = LV_SYMBOL_LIST, + .type = Type::System, + .createApp = create +}; + +} // namespace diff --git a/Tactility/Source/lvgl/Toolbar.cpp b/Tactility/Source/lvgl/Toolbar.cpp index f879fb81..48475e5d 100644 --- a/Tactility/Source/lvgl/Toolbar.cpp +++ b/Tactility/Source/lvgl/Toolbar.cpp @@ -53,13 +53,16 @@ lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title) { auto* toolbar = (Toolbar*)obj; - obj_set_style_no_padding(obj); + lv_obj_set_style_pad_all(obj, 0, 0); + lv_obj_set_style_pad_gap(obj, 0, 0); + lv_obj_center(obj); lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); toolbar->close_button = lv_button_create(obj); lv_obj_set_size(toolbar->close_button, TOOLBAR_HEIGHT - 4, TOOLBAR_HEIGHT - 4); - obj_set_style_no_padding(toolbar->close_button); + lv_obj_set_style_pad_all(toolbar->close_button, 0, 0); + lv_obj_set_style_pad_gap(toolbar->close_button, 0, 0); toolbar->close_button_image = lv_image_create(toolbar->close_button); lv_obj_align(toolbar->close_button_image, LV_ALIGN_CENTER, 0, 0); diff --git a/Tactility/Source/service/gui/Gui.cpp b/Tactility/Source/service/gui/Gui.cpp index d8868eba..77faa649 100644 --- a/Tactility/Source/service/gui/Gui.cpp +++ b/Tactility/Source/service/gui/Gui.cpp @@ -47,13 +47,14 @@ Gui* gui_alloc() { lv_obj_t* vertical_container = lv_obj_create(screen_root); lv_obj_set_size(vertical_container, LV_PCT(100), LV_PCT(100)); lv_obj_set_flex_flow(vertical_container, LV_FLEX_FLOW_COLUMN); - lvgl::obj_set_style_no_padding(vertical_container); + lv_obj_set_style_pad_all(vertical_container, 0, 0); + lv_obj_set_style_pad_gap(vertical_container, 0, 0); lvgl::obj_set_style_bg_blacken(vertical_container); instance->statusbarWidget = lvgl::statusbar_create(vertical_container); auto* app_container = lv_obj_create(vertical_container); - lvgl::obj_set_style_no_padding(app_container); + lv_obj_set_style_pad_all(app_container, 0, 0); lv_obj_set_style_border_width(app_container, 0, 0); lvgl::obj_set_style_bg_blacken(app_container); lv_obj_set_width(app_container, LV_PCT(100)); diff --git a/TactilityCore/Include/Tactility/StringUtils.h b/TactilityCore/Include/Tactility/StringUtils.h index 0e03b574..3fefeb38 100644 --- a/TactilityCore/Include/Tactility/StringUtils.h +++ b/TactilityCore/Include/Tactility/StringUtils.h @@ -58,14 +58,10 @@ std::basic_string lowercase(const std::basic_string& input) { return std::move(output); } -/** - * @return true when input only has hex characters: [a-z], [A-Z], [0-9] - */ +/** @return true when input only has hex characters: [a-z], [A-Z], [0-9] */ bool isAsciiHexString(const std::string& input); -/** - * @return the first part of a file name right up (and excluding) the first period character. - */ +/** @return the first part of a file name right up (and excluding) the first period character. */ std::string removeFileExtension(const std::string& input); } // namespace diff --git a/TactilityCore/Source/Thread.cpp b/TactilityCore/Source/Thread.cpp index 7b66b4d3..3bff0ede 100644 --- a/TactilityCore/Source/Thread.cpp +++ b/TactilityCore/Source/Thread.cpp @@ -50,12 +50,14 @@ void Thread::mainBody(void* context) { assert(pvTaskGetThreadLocalStoragePointer(nullptr, 0) == nullptr); vTaskSetThreadLocalStoragePointer(nullptr, 0, thread); + TT_LOG_I(TAG, "Starting %s", thread->name.c_str()); assert(thread->state == Thread::State::Starting); thread->setState(Thread::State::Running); thread->callbackResult = thread->callback(thread->callbackContext); assert(thread->state == Thread::State::Running); thread->setState(Thread::State::Stopped); + TT_LOG_I(TAG, "Stopped %s", thread->name.c_str()); vTaskSetThreadLocalStoragePointer(nullptr, 0, nullptr); thread->taskHandle = nullptr; diff --git a/TactilityHeadless/Include/Tactility/hal/uart/Uart.h b/TactilityHeadless/Include/Tactility/hal/uart/Uart.h index 1dc18127..f3397a35 100644 --- a/TactilityHeadless/Include/Tactility/hal/uart/Uart.h +++ b/TactilityHeadless/Include/Tactility/hal/uart/Uart.h @@ -121,4 +121,6 @@ public: */ std::unique_ptr open(std::string name); +std::vector getNames(); + } // namespace tt::hal::uart diff --git a/TactilityHeadless/Source/PreferencesEsp.cpp b/TactilityHeadless/Source/PreferencesEsp.cpp index f75f8de0..bf75721f 100644 --- a/TactilityHeadless/Source/PreferencesEsp.cpp +++ b/TactilityHeadless/Source/PreferencesEsp.cpp @@ -12,6 +12,7 @@ namespace tt { bool Preferences::optBool(const std::string& key, bool& out) const { nvs_handle_t handle; if (nvs_open(namespace_, NVS_READWRITE, &handle) != ESP_OK) { + TT_LOG_E(TAG, "Failed to open namespace %s", namespace_); return false; } else { uint8_t out_number; @@ -27,6 +28,7 @@ bool Preferences::optBool(const std::string& key, bool& out) const { bool Preferences::optInt32(const std::string& key, int32_t& out) const { nvs_handle_t handle; if (nvs_open(namespace_, NVS_READWRITE, &handle) != ESP_OK) { + TT_LOG_E(TAG, "Failed to open namespace %s", namespace_); return false; } else { bool success = nvs_get_i32(handle, key.c_str(), &out) == ESP_OK; @@ -38,6 +40,7 @@ bool Preferences::optInt32(const std::string& key, int32_t& out) const { bool Preferences::optString(const std::string& key, std::string& out) const { nvs_handle_t handle; if (nvs_open(namespace_, NVS_READWRITE, &handle) != ESP_OK) { + TT_LOG_E(TAG, "Failed to open namespace %s", namespace_); return false; } else { size_t out_size = 256; @@ -69,11 +72,11 @@ void Preferences::putBool(const std::string& key, bool value) { nvs_handle_t handle; if (nvs_open(namespace_, NVS_READWRITE, &handle) == ESP_OK) { if (nvs_set_u8(handle, key.c_str(), (uint8_t)value) != ESP_OK) { - TT_LOG_E(TAG, "failed to write %s:%s", namespace_, key.c_str()); + TT_LOG_E(TAG, "Failed to write %s:%s", namespace_, key.c_str()); } nvs_close(handle); } else { - TT_LOG_E(TAG, "failed to open namespace %s for writing", namespace_); + TT_LOG_E(TAG, "Failed to open namespace %s", namespace_); } } @@ -81,11 +84,11 @@ void Preferences::putInt32(const std::string& key, int32_t value) { nvs_handle_t handle; if (nvs_open(namespace_, NVS_READWRITE, &handle) == ESP_OK) { if (nvs_set_i32(handle, key.c_str(), value) != ESP_OK) { - TT_LOG_E(TAG, "failed to write %s:%s", namespace_, key.c_str()); + TT_LOG_E(TAG, "Failed to write %s:%s", namespace_, key.c_str()); } nvs_close(handle); } else { - TT_LOG_E(TAG, "failed to open namespace %s for writing", namespace_); + TT_LOG_E(TAG, "Failed to open namespace %s", namespace_); } } @@ -95,7 +98,7 @@ void Preferences::putString(const std::string& key, const std::string& text) { nvs_set_str(handle, key.c_str(), text.c_str()); nvs_close(handle); } else { - TT_LOG_E(TAG, "failed to open namespace %s for writing", namespace_); + TT_LOG_E(TAG, "Failed to open namespace %s", namespace_); } } diff --git a/TactilityHeadless/Source/hal/gps/GpsDevice.cpp b/TactilityHeadless/Source/hal/gps/GpsDevice.cpp index cc90b62e..948d1e40 100644 --- a/TactilityHeadless/Source/hal/gps/GpsDevice.cpp +++ b/TactilityHeadless/Source/hal/gps/GpsDevice.cpp @@ -101,7 +101,7 @@ int32_t GpsDevice::threadMain() { if (bytes_read > 0U) { - TT_LOG_I(TAG, "%s", buffer); + TT_LOG_D(TAG, "%s", buffer); switch (minmea_sentence_id((char*)buffer, false)) { case MINMEA_SENTENCE_RMC: diff --git a/TactilityHeadless/Source/hal/uart/Uart.cpp b/TactilityHeadless/Source/hal/uart/Uart.cpp index ab05a8d1..1c8681b8 100644 --- a/TactilityHeadless/Source/hal/uart/Uart.cpp +++ b/TactilityHeadless/Source/hal/uart/Uart.cpp @@ -4,12 +4,15 @@ #include #include +#include #ifdef ESP_PLATFORM -#include +#include "Tactility/TactilityHeadless.h" #include "Tactility/hal/uart/UartEsp.h" +#include #else #include "Tactility/hal/uart/UartPosix.h" +#include #endif #define TAG "uart" @@ -39,15 +42,8 @@ bool init(const std::vector& configurations) { } bool Uart::writeString(const char* buffer, TickType_t timeout) { - while (*buffer != 0) { - if (writeBytes(reinterpret_cast(buffer), 1, timeout)) { - buffer++; - } else { - TT_LOG_E(TAG, "Failed to write - breaking off"); - return false; - } - } - + auto size = strlen(buffer); + writeBytes((std::byte*)buffer, size, timeout); return true; } @@ -105,6 +101,8 @@ size_t Uart::readUntil(std::byte* buffer, size_t bufferSize, uint8_t untilByte, } std::unique_ptr open(std::string name) { + TT_LOG_I(TAG, "Open %s", name.c_str()); + auto result = std::views::filter(uartEntries, [&name](auto& entry) { return entry.configuration.name == name; }); @@ -123,10 +121,12 @@ std::unique_ptr open(std::string name) { auto uart = create(entry.configuration); assert(uart != nullptr); entry.usageId = uart->getId(); + TT_LOG_I(TAG, "Opened %lu", entry.usageId); return uart; } void close(uint32_t uartId) { + TT_LOG_I(TAG, "Close %lu", uartId); auto result = std::views::filter(uartEntries, [&uartId](auto& entry) { return entry.usageId == uartId; }); @@ -139,6 +139,32 @@ void close(uint32_t uartId) { } } +std::vector getNames() { + std::vector names; +#ifdef ESP_PLATFORM + for (auto& config : getConfiguration()->uart) { + names.push_back(config.name); + } +#else + DIR* dir = opendir("/dev"); + if (dir == nullptr) { + TT_LOG_E(TAG, "Failed to read /dev"); + return names; + } + struct dirent* current_entry; + while ((current_entry = readdir(dir)) != nullptr) { + auto name = std::string(current_entry->d_name); + if (name.starts_with("tty")) { + auto path = std::string("/dev/") + name; + names.push_back(path); + } + } + + closedir(dir); +#endif + return names; +} + Uart::Uart() : id(++lastUartId) {} Uart::~Uart() { diff --git a/TactilityHeadless/Source/hal/uart/UartEsp.cpp b/TactilityHeadless/Source/hal/uart/UartEsp.cpp index 9715864c..e558d2c2 100644 --- a/TactilityHeadless/Source/hal/uart/UartEsp.cpp +++ b/TactilityHeadless/Source/hal/uart/UartEsp.cpp @@ -13,11 +13,13 @@ namespace tt::hal::uart { bool UartEsp::start() { + TT_LOG_I(TAG, "[%s] Starting", configuration.name.c_str()); + auto lock = mutex.asScopedLock(); lock.lock(); if (started) { - TT_LOG_E(TAG, "(%d) Starting: Already started", configuration.port); + TT_LOG_E(TAG, "[%s] Starting: Already started", configuration.name.c_str()); return false; } @@ -30,46 +32,53 @@ bool UartEsp::start() { esp_err_t result = uart_param_config(configuration.port, &configuration.config); if (result != ESP_OK) { - TT_LOG_E(TAG, "(%d) Starting: Failed to configure: %s", configuration.port, esp_err_to_name(result)); + TT_LOG_E(TAG, "[%s] Starting: Failed to configure: %s", configuration.name.c_str(), esp_err_to_name(result)); return false; } + if (uart_is_driver_installed(configuration.port)) { + TT_LOG_W(TAG, "[%s] Driver was still installed. You probably forgot to stop, or another system uses/used the driver.", configuration.name.c_str()); + uart_driver_delete(configuration.port); + } + result = uart_set_pin(configuration.port, configuration.txPin, configuration.rxPin, configuration.rtsPin, configuration.ctsPin); if (result != ESP_OK) { - TT_LOG_E(TAG, "(%d) Starting: Failed set pins: %s", configuration.port, esp_err_to_name(result)); + TT_LOG_E(TAG, "[%s] Starting: Failed set pins: %s", configuration.name.c_str(), esp_err_to_name(result)); return false; } result = uart_driver_install(configuration.port, (int)configuration.rxBufferSize, (int)configuration.txBufferSize, 0, nullptr, intr_alloc_flags); if (result != ESP_OK) { - TT_LOG_E(TAG, "(%d) Starting: Failed to install driver: %s", configuration.port, esp_err_to_name(result)); + TT_LOG_E(TAG, "[%s] Starting: Failed to install driver: %s", configuration.name.c_str(), esp_err_to_name(result)); return false; } started = true; - TT_LOG_I(TAG, "(%d) Started", configuration.port); + TT_LOG_I(TAG, "[%s] Started", configuration.name.c_str()); return true; } bool UartEsp::stop() { + TT_LOG_I(TAG, "[%s] Stopping", configuration.name.c_str()); + auto lock = mutex.asScopedLock(); lock.lock(); if (!started) { - TT_LOG_E(TAG, "(%d) Stopping: Not started", configuration.port); + TT_LOG_E(TAG, "[%s] Stopping: Not started", configuration.name.c_str()); return false; } esp_err_t result = uart_driver_delete(configuration.port); if (result != ESP_OK) { - TT_LOG_E(TAG, "(%d) Stopping: Failed to delete driver: %s", configuration.port, esp_err_to_name(result)); + TT_LOG_E(TAG, "[%s] Stopping: Failed to delete driver: %s", configuration.name.c_str(), esp_err_to_name(result)); return false; } started = false; - TT_LOG_I(TAG, "(%d) Stopped", configuration.port); + TT_LOG_I(TAG, "[%s] Stopped", configuration.name.c_str()); return true; } @@ -97,7 +106,8 @@ bool UartEsp::readByte(std::byte* output, TickType_t timeout) { } size_t UartEsp::writeBytes(const std::byte* buffer, size_t bufferSize, TickType_t timeout) { - if (!mutex.lock(timeout)) { + auto lock = mutex.asScopedLock(); + if (!lock.lock(timeout)) { return false; } diff --git a/TactilityHeadless/Source/hal/uart/UartPosix.cpp b/TactilityHeadless/Source/hal/uart/UartPosix.cpp index d8416fe7..78af4109 100644 --- a/TactilityHeadless/Source/hal/uart/UartPosix.cpp +++ b/TactilityHeadless/Source/hal/uart/UartPosix.cpp @@ -19,13 +19,13 @@ bool UartPosix::start() { lock.lock(); if (device != nullptr) { - TT_LOG_E(TAG, "(%s) Starting: Already started", configuration.name.c_str()); + TT_LOG_E(TAG, "[%s] Starting: Already started", configuration.name.c_str()); return false; } auto file = fopen(configuration.name.c_str(), "w"); if (file == nullptr) { - TT_LOG_E(TAG, "(%s) failed to open", configuration.name.c_str()); + TT_LOG_E(TAG, "[%s] Open device failed", configuration.name.c_str()); return false; } @@ -33,18 +33,16 @@ bool UartPosix::start() { struct termios tty; if (tcgetattr(fileno(file), &tty) < 0) { - printf("(%s) tcgetattr failed: %s\n", configuration.name.c_str(), strerror(errno)); + printf("[%s] tcgetattr failed: %s\n", configuration.name.c_str(), strerror(errno)); return false; } if (cfsetospeed(&tty, (speed_t)configuration.baudRate) == -1) { - TT_LOG_E(TAG, "(%s) failed to set output speed", configuration.name.c_str()); - return false; + TT_LOG_E(TAG, "[%s] Setting output speed failed", configuration.name.c_str()); } if (cfsetispeed(&tty, (speed_t)configuration.baudRate) == -1) { - TT_LOG_E(TAG, "(%s) failed to set input speed", configuration.name.c_str()); - return false; + TT_LOG_E(TAG, "[%s] Setting input speed failed", configuration.name.c_str()); } tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */ @@ -63,13 +61,13 @@ bool UartPosix::start() { tty.c_cc[VTIME] = 1; if (tcsetattr(fileno(file), TCSANOW, &tty) != 0) { - printf("(%s) tcsetattr failed: %s\n", configuration.name.c_str(), strerror(errno)); + printf("[%s] tcsetattr failed: %s\n", configuration.name.c_str(), strerror(errno)); return false; } device = std::move(new_device); - TT_LOG_I(TAG, "(%s) Started", configuration.name.c_str()); + TT_LOG_I(TAG, "[%s] Started", configuration.name.c_str()); return true; } @@ -78,13 +76,13 @@ bool UartPosix::stop() { lock.lock(); if (device == nullptr) { - TT_LOG_E(TAG, "(%s) Stopping: Not started", configuration.name.c_str()); + TT_LOG_E(TAG, "[%s] Stopping: Not started", configuration.name.c_str()); return false; } device = nullptr; - TT_LOG_I(TAG, "(%s) Stopped", configuration.name.c_str()); + TT_LOG_I(TAG, "[%s] Stopped", configuration.name.c_str()); return true; } @@ -141,7 +139,7 @@ void UartPosix::flushInput() { uint32_t UartPosix::getBaudRate() { struct termios tty; if (tcgetattr(fileno(device.get()), &tty) < 0) { - printf("(%s) tcgetattr failed: %s\n", configuration.name.c_str(), strerror(errno)); + printf("[%s] tcgetattr failed: %s\n", configuration.name.c_str(), strerror(errno)); return false; } else { return (uint32_t)cfgetispeed(&tty); @@ -156,17 +154,17 @@ bool UartPosix::setBaudRate(uint32_t baudRate, TickType_t timeout) { struct termios tty; if (tcgetattr(fileno(device.get()), &tty) < 0) { - printf("(%s) tcgetattr failed: %s\n", configuration.name.c_str(), strerror(errno)); + printf("[%s] tcgetattr failed: %s\n", configuration.name.c_str(), strerror(errno)); return false; } if (cfsetospeed(&tty, (speed_t)configuration.baudRate) == -1) { - TT_LOG_E(TAG, "(%s) failed to set output speed", configuration.name.c_str()); + TT_LOG_E(TAG, "[%s] Failed to set output speed", configuration.name.c_str()); return false; } if (cfsetispeed(&tty, (speed_t)configuration.baudRate) == -1) { - TT_LOG_E(TAG, "(%s) failed to set input speed", configuration.name.c_str()); + TT_LOG_E(TAG, "[%s] Failed to set input speed", configuration.name.c_str()); return false; }