/* ; Project: Open Vehicle Monitor System ; Date: 14th March 2017 ; ; Changes: ; 1.0 Initial release ; ; (C) 2011 Michael Stegen / Stegen Electronics ; (C) 2011-2017 Mark Webb-Johnson ; (C) 2011 Sonny Chen @ EPRO/DX ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in ; all copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ; THE SOFTWARE. */ #include #include #include #include "ovms_log.h" #include "ovms_console.h" #include "ovms_version.h" #include "log_buffers.h" //static const char *TAG = "Console"; static char CRbuf[4] = { '\r', '\033', '[', 'K' }; static char NLbuf[2] = { '\r', '\n' }; static char ctrlRbuf[1] = { 'R'-0100 }; char ** Complete (microrl_t* rl, int argc, const char * const * argv ) { return MyCommandApp.Complete((OvmsWriter*)rl->userdata, argc, argv); } OvmsConsole::OvmsConsole() : OvmsShell(COMMAND_RESULT_VERBOSE) { m_ready = false; m_deferred = NULL; m_discarded = 0; m_state = AT_PROMPT; m_lost = m_acked = 0; } OvmsConsole::~OvmsConsole() { m_ready = false; MyCommandApp.DeregisterConsole(this); } void OvmsConsole::Initialize(const char* console) { OvmsShell::Initialize(console != NULL); microrl_set_complete_callback(&m_rl, Complete); if (console) { printf("\nWelcome to the Open Vehicle Monitoring System (OVMS) - %s Console\n", console); printf("Firmware: %s\nHardware: %s\n",GetOVMSVersion().c_str(),GetOVMSHardware().c_str()); ProcessChar('\n'); MyCommandApp.RegisterConsole(this); } m_ready = true; } char** OvmsConsole::SetCompletion(int index, const char* token) { if (index < COMPLETION_MAX_TOKENS+1) { if (index == COMPLETION_MAX_TOKENS) token = "..."; if (token) { strncpy(m_space[index], token, TOKEN_MAX_LENGTH-1); m_space[index][TOKEN_MAX_LENGTH-1] = '\0'; m_completions[index] = m_space[index]; ++index; } m_completions[index] = NULL; } return m_completions; } void OvmsConsole::Log(LogBuffers* message) { if (!m_ready) { message->release(); return; } Event event; event.type = ALERT_MULTI; event.multi = message; BaseType_t ret = xQueueSendToBack(m_queue, (void * )&event, 0); if (ret != pdPASS) { message->release(); ++m_lost; } } void OvmsConsole::Service() { vTaskDelay(50 / portTICK_PERIOD_MS); for (;;) { Poll(portMAX_DELAY); } } void OvmsConsole::Poll(portTickType ticks, QueueHandle_t queue) { Event event; if (!queue) queue = m_queue; for (;;) { // Waiting for UART RX event or async log message event if (xQueueReceive(queue, (void*)&event, ticks)) { if (event.type <= RECV) { if (m_state != AT_PROMPT) ProcessChars(ctrlRbuf, 1); // Restore the prompt plus any type-in on a new line m_state = AT_PROMPT; HandleDeviceEvent(&event); continue; } // While a command that takes input is executing, put alert events into a // separate "deferred" queue. If that queue fills, keep only the last N // events and count those discarded. if (m_insert) { if (!m_deferred) m_deferred = xQueueCreate(CONFIG_OVMS_HW_CONSOLE_QUEUE_SIZE, sizeof(Event)); if (xQueueSendToBack(m_deferred, (void *)&event, 0) != pdPASS) { Event discard; xQueueReceive(m_deferred, (void*)&discard, 0); xQueueSendToBack(m_deferred, (void *)&event, 0); if (discard.type == ALERT_MULTI) discard.multi->release(); else free(discard.buffer); ++m_discarded; } continue; } // We remove the newline from the end of a log message so that we can later // output a newline as part of restoring the command prompt and its line // without leaving a blank line above it. So before we display a new log // message we need to output a newline if the last action was displaying a // log message, or output a carriage return to back over the prompt. if (m_monitoring && (event.type != ALERT_MULTI || event.multi->begin() != event.multi->end())) { if (m_state == AWAITING_NL) write(NLbuf, 2); else if (m_state == AT_PROMPT) write(CRbuf, 4); char* buffer; size_t len; if (event.type == ALERT_MULTI) { LogBuffers::iterator before = event.multi->begin(), after; while (true) { buffer = *before; len = strlen(buffer); after = before; ++after; if (after == event.multi->end()) break; write(buffer, len); before = after; } } else { buffer = event.buffer; len = strlen(buffer); } if (buffer[len-1] == '\n') { --len; if (buffer[len-1] == '\r') // Omit CR, too, in case of \r\n --len; m_state = AWAITING_NL; write(buffer, len); } else { m_state = NO_NL; write(buffer, len); } } if (event.type == ALERT_MULTI) event.multi->release(); else free(event.buffer); ticks = 200 / portTICK_PERIOD_MS; } else { // Timeout indicates the queue is empty unsigned int lost = m_lost - m_acked; // Modulo 2^32 arithmetic if (lost > 0) { if (m_state == AWAITING_NL) write(NLbuf, 2); else if (m_state == AT_PROMPT) write(CRbuf, 4); printf("\033[33m[%d log messages lost]\033[0m", lost); m_state = AWAITING_NL; m_acked += lost; continue; } if (m_state != AT_PROMPT) ProcessChars(ctrlRbuf, 1); // Restore the prompt plus any type-in on a new line m_state = AT_PROMPT; return; } } } void OvmsConsole::finalise() { if (m_deferred) { if (m_discarded) { printf("\033[33m[%d log messages discarded]\033[0m", m_discarded); m_state = AWAITING_NL; m_discarded = 0; } Poll(0, m_deferred); vQueueDelete(m_deferred); m_deferred = NULL; } }