// (C) 2018-2024 by Folkert van Heusden // Released under MIT license #include #include #include #include #include #include #include #include #include "console.h" #include "gen.h" #include "log.h" #include "utils.h" #if defined(BUILD_FOR_RP2040) #include "rp2040.h" void thread_wrapper_console(void *p) { console *const consolel = reinterpret_cast(p); consolel->operator()(); } #endif console::console(std::atomic_uint32_t *const stop_event, const int t_width, const int t_height) : stop_event(stop_event), t_width(t_width), t_height(t_height) { screen_buffer = new char[t_width * t_height](); #if defined(BUILD_FOR_RP2040) xSemaphoreGive(input_lock); // initialize #endif } console::~console() { // done as well in subclasses but also here to // stop lgtm.com complaining about it stop_thread(); delete [] screen_buffer; } void console::begin() { } void console::start_thread() { assert(b); stop_thread_flag = false; #if defined(BUILD_FOR_RP2040) xTaskCreate(&thread_wrapper_console, "console", 2048, this, 1, nullptr); #else th = new std::thread(std::ref(*this)); #endif } void console::stop_thread() { #if !defined(ESP32) && !defined(BUILD_FOR_RP2040) if (th) { stop_thread_flag = true; th->join(); delete th; th = nullptr; } #endif } bool console::poll_char() { #if defined(BUILD_FOR_RP2040) xSemaphoreTake(input_lock, portMAX_DELAY); #else std::unique_lock lck(input_lock); #endif bool rc = input_buffer.empty() == false; #if defined(BUILD_FOR_RP2040) xSemaphoreGive(input_lock); #endif return rc; } int console::get_char() { #if defined(BUILD_FOR_RP2040) xSemaphoreTake(input_lock, portMAX_DELAY); #else std::unique_lock lck(input_lock); #endif char c = -1; if (input_buffer.empty() == false) { c = input_buffer.at(0); input_buffer.erase(input_buffer.begin() + 0); } #if defined(BUILD_FOR_RP2040) xSemaphoreGive(input_lock); #endif return c; } std::optional console::wait_char(const int timeout_ms) { #if defined(BUILD_FOR_RP2040) uint8_t rc = 0; if (xQueueReceive(have_data, &rc, timeout_ms / portTICK_PERIOD_MS) == pdFALSE || rc == 0) return { }; std::optional c { }; xSemaphoreTake(input_lock, portMAX_DELAY); if (input_buffer.empty() == false) { c = input_buffer.at(0); input_buffer.erase(input_buffer.begin() + 0); } xSemaphoreGive(input_lock); return c; #else std::unique_lock lck(input_lock); using namespace std::chrono_literals; if (input_buffer.empty() == false || have_data.wait_for(lck, timeout_ms * 1ms) == std::cv_status::no_timeout) { if (input_buffer.empty() == false) { int c = input_buffer.at(0); input_buffer.erase(input_buffer.begin() + 0); return c; } } return { }; #endif } void console::flush_input() { #if defined(BUILD_FOR_RP2040) xSemaphoreTake(input_lock, portMAX_DELAY); #else std::unique_lock lck(input_lock); #endif input_buffer.clear(); #if defined(BUILD_FOR_RP2040) xSemaphoreGive(input_lock); #endif } void console::emit_backspace() { put_char(8); put_char(' '); put_char(8); } std::string console::read_line(const std::string & prompt) { put_string(prompt); put_string(">"); while(edit_lines_hist.size() >= n_edit_lines_hist) edit_lines_hist.erase(edit_lines_hist.begin()); while(edit_lines_hist.size() < n_edit_lines_hist) edit_lines_hist.push_back(""); size_t line_nr = edit_lines_hist.size() - 1; bool escape = false; for(;;) { auto c = wait_char(250); if (*stop_event == EVENT_TERMINATE) return ""; if (c.has_value() == false) continue; if (c == 27) { escape = true; continue; } if (escape) { if (c == '[') continue; escape = false; for(size_t i=0; i 0) line_nr--; } else if (c == 'B') { // down if (line_nr < edit_lines_hist.size() - 1) line_nr++; } else { continue; } for(size_t i=0; i= 32) { edit_lines_hist.at(line_nr) += c.value(); put_char(c.value()); } } put_string_lf(""); if (line_nr != edit_lines_hist.size() - 1) edit_lines_hist.push_back(edit_lines_hist.at(line_nr)); return edit_lines_hist.at(line_nr); } void console::put_char(const char c) { put_char_ll(c); if (c == 0) { // ignore these } else if (c == 13) tx = 0; else if (c == 10) { if (debug_buffer.empty() == false) { TRACE("TTY: %s", debug_buffer.c_str()); debug_buffer.clear(); } if (timestamps) { char buffer[32] { }; uint64_t timestamp = get_us() - start_ts; uint64_t seconds = timestamp / 1000000; int len = snprintf(buffer, sizeof buffer, "%02d:%02d:%02d.%06d ", int(seconds / 3600), int((seconds / 60) % 60), int(seconds % 60), int(timestamp % 1000000)); for(int i=0; i 0) tx--; } else { screen_buffer[ty * t_width + tx++] = c; if (tx == t_width) { tx = 0; ty++; } if (debug_buffer.size() < 4096) debug_buffer += c; } if (ty == t_height) { memmove(&screen_buffer[0 * t_width], &screen_buffer[1 * t_width], sizeof(char) * t_width * (t_height - 1)); ty--; memset(&screen_buffer[(t_height - 1) * t_width], ' ', t_width); } } void console::put_string(const std::string & what) { for(size_t x=0; x lck(input_lock); #endif input_buffer.push_back(c); #if defined(BUILD_FOR_RP2040) xSemaphoreGive(input_lock); uint8_t value = 1; if (xQueueSend(have_data, &value, portMAX_DELAY) == pdFALSE) TRACE("xQueueSend failed"); #else have_data.notify_all(); #endif } } TRACE("Console thread terminating"); }