OVMS3/OVMS.V3/components/ovms_webserver/src/ovms_commandstream.cpp

273 lines
8.4 KiB
C++

/*
; Project: Open Vehicle Monitor System
; Date: 14th March 2017
;
; Changes:
; 1.0 Initial release
;
; (C) 2018 Michael Balzer
;
; 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.
*/
// We're using ESP_EARLY_LOG* (direct USB console output) for protocol debug logging.
// To enable protocol debug logging locally, uncomment:
// #define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "ovms_log.h"
static const char *TAG = "webcommand";
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "buffered_shell.h"
#include "log_buffers.h"
#include "ovms_webserver.h"
#include "ovms_script.h"
#include "ovms_module.h"
struct hcs_writebuf
{
char* data;
size_t len;
};
HttpCommandStream::HttpCommandStream(mg_connection* nc, extram::string command,
bool javascript /*=false*/, int verbosity /*=COMMAND_RESULT_NORMAL*/)
: OvmsShell(verbosity), MgHandler(nc)
// Note: due to a gcc bug, the base classes MUST be done in this order,
// or compilation will fail with "error: generic thunk code fails for method […] printf".
// See https://bugzilla.redhat.com/show_bug.cgi?id=1511021
// and https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83549
// for details.
{
ESP_EARLY_LOGD(TAG, "HttpCommandStream[%p] init: handler=%p command='%s%s' verbosity=%d", nc, this,
command.substr(0,200).c_str(), (command.length()>200) ? " [...]" : "", verbosity);
m_command = command;
m_javascript = javascript;
m_done = false;
m_sent = m_ack = 0;
Initialize(false);
SetSecure(true); // Note: assuming user is admin
// create write queue & command task:
m_writequeue = xQueueCreate(30, sizeof(hcs_writebuf));
char name[configMAX_TASK_NAME_LEN];
snprintf(name, sizeof(name), "%s", command.c_str());
xTaskCreatePinnedToCore(CommandTask, name,
CONFIG_OVMS_SYS_COMMAND_STACK_SIZE, (void*)this,
CONFIG_OVMS_SYS_COMMAND_PRIORITY, &m_cmdtask, CORE(1));
AddTaskToMap(m_cmdtask);
}
HttpCommandStream::~HttpCommandStream()
{
hcs_writebuf wbuf;
if (m_writequeue) {
while (xQueueReceive(m_writequeue, &wbuf, 0) == pdTRUE)
free(wbuf.data);
vQueueDelete(m_writequeue);
}
}
void HttpCommandStream::Initialize(bool print)
{
if (!m_javascript) {
OvmsShell::Initialize(print);
ProcessChar('\n');
}
}
void HttpCommandStream::CommandTask(void* object)
{
HttpCommandStream* me = (HttpCommandStream*) object;
ESP_LOGI(TAG, "HttpCommandStream[%p]: %d bytes free, executing: %s%s",
me->m_nc, heap_caps_get_free_size(MALLOC_CAP_8BIT),
me->m_command.substr(0,200).c_str(), (me->m_command.length()>200) ? " [...]" : "");
// execute command:
if (me->m_javascript) {
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
MyDuktape.DuktapeEvalNoResult(me->m_command.c_str(), me);
#else
me->puts("ERROR: Javascript support disabled");
#endif
} else {
me->ProcessChars(me->m_command.data(), me->m_command.size());
me->ProcessChar('\n');
}
me->m_done = true;
#if MG_ENABLE_BROADCAST && WEBSRV_USE_MG_BROADCAST
if (m_writequeue && uxQueueMessagesWaiting(me->m_writequeue) > 0) {
ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] RequestPollLast, qlen=%d done=%d sent=%d ack=%d", me->m_nc, uxQueueMessagesWaiting(me->m_writequeue), me->m_done, me->m_sent, me->m_ack);
me->RequestPoll();
ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] RequestPollDone, qlen=%d done=%d sent=%d ack=%d", me->m_nc, uxQueueMessagesWaiting(me->m_writequeue), me->m_done, me->m_sent, me->m_ack);
}
#endif // MG_ENABLE_BROADCAST && WEBSRV_USE_MG_BROADCAST
while (me->m_nc)
vTaskDelay(10/portTICK_PERIOD_MS);
delete me;
vTaskDelete(NULL);
}
void HttpCommandStream::ProcessQueue()
{
size_t txlen = 0;
hcs_writebuf wbuf;
if (m_writequeue) {
while (txlen < XFER_CHUNK_SIZE && xQueueReceive(m_writequeue, &wbuf, 0) == pdTRUE) {
if (m_nc) {
mg_send_http_chunk(m_nc, wbuf.data, wbuf.len);
txlen += wbuf.len;
}
free(wbuf.data);
}
if (txlen) {
m_sent += txlen;
ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] ProcessQueue txlen=%d, qlen=%d done=%d sent=%d ack=%d",
m_nc, txlen, uxQueueMessagesWaiting(m_writequeue), m_done, m_sent, m_ack);
}
}
if (m_done && m_sent == m_ack) {
ESP_EARLY_LOGD(TAG, "HttpCommandStream[%p] DONE, %d bytes sent, %d bytes free",
m_nc, m_sent, heap_caps_get_free_size(MALLOC_CAP_8BIT));
if (m_nc) {
m_nc->flags |= MG_F_SEND_AND_CLOSE; // necessary to prevent mg_broadcast lockups
mg_send_http_chunk(m_nc, "", 0);
m_nc->user_data = NULL;
m_nc = NULL;
}
}
}
int HttpCommandStream::HandleEvent(int ev, void* p)
{
switch (ev)
{
case MG_EV_POLL:
// check for new transmission:
ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] EV_POLL qlen=%d done=%d sent=%d ack=%d",
m_nc, m_writequeue ? uxQueueMessagesWaiting(m_writequeue) : -1, m_done, m_sent, m_ack);
if (m_ack == m_sent)
ProcessQueue();
break;
case MG_EV_SEND:
// last transmission has finished:
ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] EV_SEND qlen=%d done=%d sent=%d ack=%d",
m_nc, m_writequeue ? uxQueueMessagesWaiting(m_writequeue) : -1, m_done, m_sent, m_ack);
m_ack = m_sent;
ProcessQueue();
break;
case MG_EV_CLOSE:
ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] EV_CLOSE qlen=%d done=%d sent=%d ack=%d",
m_nc, m_writequeue ? uxQueueMessagesWaiting(m_writequeue) : -1, m_done, m_sent, m_ack);
// connection has been closed, possibly externally:
// we need to let the command task finish normally to prevent problems
// due to lost/locked ressources, so we just detach:
m_nc->user_data = NULL;
m_nc = NULL;
ProcessQueue(); // empty queue (no tx) to prevent task lockup on write
ev = 0; // prevent deletion by main event handler
break;
default:
break;
}
return ev;
}
int HttpCommandStream::puts(const char* s)
{
if (!m_nc)
return 0;
write(s, strlen(s));
write("\n", 1);
return 0;
}
int HttpCommandStream::printf(const char* fmt, ...)
{
if (!m_nc)
return 0;
char *buffer = NULL;
va_list args;
va_start(args, fmt);
int ret = vasprintf(&buffer, fmt, args);
va_end(args);
if (ret >= 0) {
write(buffer, ret);
free(buffer);
}
return ret;
}
ssize_t HttpCommandStream::write(const void *buf, size_t nbyte)
{
if (!m_nc || nbyte == 0)
return 0;
if (!m_writequeue)
return nbyte;
hcs_writebuf wbuf;
wbuf.data = (char*) ExternalRamMalloc(nbyte);
wbuf.len = nbyte;
memcpy(wbuf.data, buf, nbyte);
if (xQueueSend(m_writequeue, &wbuf, portMAX_DELAY) != pdTRUE) {
free(wbuf.data);
return nbyte;
}
#if MG_ENABLE_BROADCAST && WEBSRV_USE_MG_BROADCAST
if (uxQueueMessagesWaiting(m_writequeue) == 1) {
ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] RequestPoll, qlen=1 done=%d sent=%d ack=%d", m_nc, m_done, m_sent, m_ack);
RequestPoll();
ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] RequestPollDone, qlen=%d done=%d sent=%d ack=%d", m_nc, uxQueueMessagesWaiting(m_writequeue), m_done, m_sent, m_ack);
}
else
#endif // MG_ENABLE_BROADCAST && WEBSRV_USE_MG_BROADCAST
ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] AddQueue, qlen=%d done=%d sent=%d ack=%d", m_nc, uxQueueMessagesWaiting(m_writequeue), m_done, m_sent, m_ack);
return nbyte;
}
void HttpCommandStream::Log(LogBuffers* message)
{
// writing could block, logging is done via the websocket stream
message->release();
}