273 lines
8.4 KiB
C++
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();
|
|
}
|