OVMS3/OVMS.V3/components/ovms_script/srcduk/ovms_duktape.cpp

1558 lines
42 KiB
C++
Raw Normal View History

/*
; 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 "ovms_log.h"
static const char *TAG = "ovms-duktape";
#include <string>
#include <fstream>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <dirent.h>
#include <esp_task_wdt.h>
#include "ovms_malloc.h"
#include "ovms_module.h"
#include "ovms_duktape.h"
#include "ovms_config.h"
#include "ovms_command.h"
#include "ovms_events.h"
#include "console_async.h"
#include "buffered_shell.h"
#include "ovms_netmanager.h"
#include "ovms_tls.h"
#ifdef CONFIG_OVMS_COMP_PLUGINS
#include "ovms_plugins.h"
#endif // #ifdef CONFIG_OVMS_COMP_PLUGINS
OvmsDuktape MyDuktape __attribute__ ((init_priority (1000)));
OvmsWriter* duktapewriter = NULL;
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
#include "umm_malloc.c"
static void *umm_memory = NULL;
#endif
////////////////////////////////////////////////////////////////////////////////
// Duktape utility functions
static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata);
static void duk__push_module_object(duk_context *ctx, const char *id, duk_bool_t main);
static duk_bool_t duk__get_cached_module(duk_context *ctx, const char *id)
{
duk_push_global_stash(ctx);
(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
if (duk_get_prop_string(ctx, -1, id))
{
duk_remove(ctx, -2);
duk_remove(ctx, -2);
return 1;
}
else
{
duk_pop_3(ctx);
return 0;
}
}
/* Place a `module` object on the top of the value stack into the require cache
* based on its `.id` property. As a convenience to the caller, leave the
* object on top of the value stack afterwards.
*/
static void duk__put_cached_module(duk_context *ctx)
{
/* [ ... module ] */
duk_push_global_stash(ctx);
(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
duk_dup(ctx, -3);
/* [ ... module stash req_cache module ] */
(void) duk_get_prop_string(ctx, -1, "id");
duk_dup(ctx, -2);
duk_put_prop(ctx, -4);
duk_pop_3(ctx); /* [ ... module ] */
}
static void duk__del_cached_module(duk_context *ctx, const char *id)
{
duk_push_global_stash(ctx);
(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
duk_del_prop_string(ctx, -1, id);
duk_pop_2(ctx);
}
static duk_ret_t duk__handle_require(duk_context *ctx)
{
/*
* Value stack handling here is a bit sloppy but should be correct.
* Call handling will clean up any extra garbage for us.
*/
const char *id;
const char *parent_id;
duk_idx_t module_idx;
duk_idx_t stash_idx;
duk_int_t ret;
duk_push_global_stash(ctx);
stash_idx = duk_normalize_index(ctx, -1);
duk_push_current_function(ctx);
(void) duk_get_prop_string(ctx, -1, "\xff" "moduleId");
parent_id = duk_require_string(ctx, -1);
(void) parent_id; /* not used directly; suppress warning */
/* [ id stash require parent_id ] */
id = duk_require_string(ctx, 0);
(void) duk_get_prop_string(ctx, stash_idx, "\xff" "modResolve");
duk_dup(ctx, 0); /* module ID */
duk_dup(ctx, -3); /* parent ID */
duk_call(ctx, 2);
/* [ ... stash ... resolved_id ] */
id = duk_require_string(ctx, -1);
if (duk__get_cached_module(ctx, id))
{
goto have_module; /* use the cached module */
}
duk__push_module_object(ctx, id, 0 /*main*/);
duk__put_cached_module(ctx); /* module remains on stack */
/*
* From here on out, we have to be careful not to throw. If it can't be
* avoided, the error must be caught and the module removed from the
* require cache before rethrowing. This allows the application to
* reattempt loading the module.
*/
module_idx = duk_normalize_index(ctx, -1);
/* [ ... stash ... resolved_id module ] */
(void) duk_get_prop_string(ctx, stash_idx, "\xff" "modLoad");
duk_dup(ctx, -3); /* resolved ID */
(void) duk_get_prop_string(ctx, module_idx, "exports");
duk_dup(ctx, module_idx);
ret = duk_pcall(ctx, 3);
if (ret != DUK_EXEC_SUCCESS)
{
duk__del_cached_module(ctx, id);
duk_throw(ctx); /* rethrow */
}
if (duk_is_string(ctx, -1))
{
duk_int_t ret;
/* [ ... module source ] */
ret = duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1);
if (ret != DUK_EXEC_SUCCESS)
{
duk__del_cached_module(ctx, id);
duk_throw(ctx); /* rethrow */
}
}
else if (duk_is_undefined(ctx, -1))
{
duk_pop(ctx);
}
else
{
duk__del_cached_module(ctx, id);
duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid module load callback return value");
}
/* fall through */
have_module:
/* [ ... module ] */
(void) duk_get_prop_string(ctx, -1, "exports");
return 1;
}
static void duk__push_require_function(duk_context *ctx, const char *id)
{
duk_push_c_function(ctx, duk__handle_require, 1);
duk_push_string(ctx, "name");
duk_push_string(ctx, "require");
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE);
duk_push_string(ctx, id);
duk_put_prop_string(ctx, -2, "\xff" "moduleId");
/* require.cache */
duk_push_global_stash(ctx);
(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
duk_put_prop_string(ctx, -3, "cache");
duk_pop(ctx);
/* require.main */
duk_push_global_stash(ctx);
(void) duk_get_prop_string(ctx, -1, "\xff" "mainModule");
duk_put_prop_string(ctx, -3, "main");
duk_pop(ctx);
}
static void duk__push_module_object(duk_context *ctx, const char *id, duk_bool_t main)
{
duk_push_object(ctx);
/* Set this as the main module, if requested */
if (main)
{
duk_push_global_stash(ctx);
duk_dup(ctx, -2);
duk_put_prop_string(ctx, -2, "\xff" "mainModule");
duk_pop(ctx);
}
/* Node.js uses the canonicalized filename of a module for both module.id
* and module.filename. We have no concept of a file system here, so just
* use the module ID for both values.
*/
duk_push_string(ctx, id);
duk_dup(ctx, -1);
duk_put_prop_string(ctx, -3, "filename");
duk_put_prop_string(ctx, -2, "id");
/* module.exports = {} */
duk_push_object(ctx);
duk_put_prop_string(ctx, -2, "exports");
/* module.loaded = false */
duk_push_false(ctx);
duk_put_prop_string(ctx, -2, "loaded");
/* module.require */
duk__push_require_function(ctx, id);
duk_put_prop_string(ctx, -2, "require");
}
static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata)
{
const char *src;
/*
* Stack: [ ... module source ]
*/
(void) udata;
/* Wrap the module code in a function expression. This is the simplest
* way to implement CommonJS closure semantics and matches the behavior of
* e.g. Node.js.
*/
duk_push_string(ctx, "(function(exports,require,module,__filename,__dirname){");
src = duk_require_string(ctx, -2);
duk_push_string(ctx, (src[0] == '#' && src[1] == '!') ? "//" : ""); /* Shebang support. */
duk_dup(ctx, -3); /* source */
duk_push_string(ctx, "\n})"); /* Newline allows module last line to contain a // comment. */
duk_concat(ctx, 4);
/* [ ... module source func_src ] */
(void) duk_get_prop_string(ctx, -3, "filename");
duk_compile(ctx, DUK_COMPILE_EVAL);
duk_call(ctx, 0);
/* [ ... module source func ] */
/* Set name for the wrapper function. */
duk_push_string(ctx, "name");
duk_push_string(ctx, "main");
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);
/* call the function wrapper */
(void) duk_get_prop_string(ctx, -3, "exports"); /* exports */
(void) duk_get_prop_string(ctx, -4, "require"); /* require */
duk_dup(ctx, -5); /* module */
(void) duk_get_prop_string(ctx, -6, "filename"); /* __filename */
duk_push_undefined(ctx); /* __dirname */
duk_call(ctx, 5);
/* [ ... module source result(ignore) ] */
/* module.loaded = true */
duk_push_true(ctx);
duk_put_prop_string(ctx, -4, "loaded");
/* [ ... module source retval ] */
duk_pop_2(ctx);
/* [ ... module ] */
return 1;
}
/* Load a module as the 'main' module. */
duk_ret_t duk_module_node_peval_main(duk_context *ctx, const char *path)
{
/*
* Stack: [ ... source ]
*/
duk__push_module_object(ctx, path, 1 /*main*/);
/* [ ... source module ] */
duk_dup(ctx, 0);
/* [ ... source module source ] */
return duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1);
}
void duk_module_node_init(duk_context *ctx)
{
/*
* Stack: [ ... options ] => [ ... ]
*/
duk_idx_t options_idx;
duk_require_object_coercible(ctx, -1); /* error before setting up requireCache */
options_idx = duk_require_normalize_index(ctx, -1);
/* Initialize the require cache to a fresh object. */
duk_push_global_stash(ctx);
duk_push_bare_object(ctx);
duk_put_prop_string(ctx, -2, "\xff" "requireCache");
duk_pop(ctx);
/* Stash callbacks for later use. User code can overwrite them later
* on directly by accessing the global stash.
*/
duk_push_global_stash(ctx);
duk_get_prop_string(ctx, options_idx, "resolve");
duk_require_function(ctx, -1);
duk_put_prop_string(ctx, -2, "\xff" "modResolve");
duk_get_prop_string(ctx, options_idx, "load");
duk_require_function(ctx, -1);
duk_put_prop_string(ctx, -2, "\xff" "modLoad");
duk_pop(ctx);
/* Stash main module. */
duk_push_global_stash(ctx);
duk_push_undefined(ctx);
duk_put_prop_string(ctx, -2, "\xff" "mainModule");
duk_pop(ctx);
/* register `require` as a global function. */
duk_push_global_object(ctx);
duk_push_string(ctx, "require");
duk__push_require_function(ctx, "");
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE |
DUK_DEFPROP_SET_WRITABLE |
DUK_DEFPROP_SET_CONFIGURABLE);
duk_pop(ctx);
duk_pop(ctx); /* pop argument */
}
////////////////////////////////////////////////////////////////////////////////
// Duktape engine task
void DukTapeLaunchTask(void *pvParameters)
{
OvmsDuktape* me = (OvmsDuktape*)pvParameters;
me->DukTapeTask();
}
void* DukOvmsAlloc(void *udata, duk_size_t size)
{
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
return umm_malloc(size);
#else
return ExternalRamMalloc(size);
#endif
}
void* DukOvmsRealloc(void *udata, void *ptr, duk_size_t size)
{
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
return umm_realloc(ptr, size);
#else
return ExternalRamRealloc(ptr, size);
#endif
}
void DukOvmsFree(void *udata, void *ptr)
{
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
umm_free(ptr);
#else
free(ptr);
#endif
}
void DukOvmsFatalHandler(void *udata, const char *msg)
{
ESP_LOGE(TAG, "Duktape fatal error: %s",msg);
abort();
}
void DukOvmsErrorHandler(duk_context *ctx, duk_idx_t err_idx, OvmsWriter *writer, const char *filename)
{
const char *error, *stack = NULL;
int linenumber = 0;
if (!filename) filename = "eval";
duk_require_stack(ctx, 1);
if (duk_is_error(ctx, err_idx))
{
duk_get_prop_string(ctx, err_idx, "fileName");
if (duk_is_string(ctx, -1))
filename = duk_get_string(ctx, -1);
duk_pop(ctx);
duk_get_prop_string(ctx, err_idx, "lineNumber");
linenumber = duk_get_number_default(ctx, -1, 0);
duk_pop(ctx);
duk_get_prop_string(ctx, err_idx, "stack");
if (duk_is_string(ctx, -1))
stack = duk_get_string(ctx, -1);
duk_pop(ctx);
}
error = stack ? stack : duk_safe_to_string(ctx, err_idx);
if (writer)
{
// some errors have line numbers
if (linenumber == 0 || strstr(error, "(line "))
writer->printf("ERROR: %s\n", error);
else
writer->printf("ERROR: %s (line %d)\n", error, linenumber);
}
else
{
ESP_LOGE(TAG, "[%s:%d] %s", filename, linenumber, error);
}
}
static duk_ret_t DukOvmsResolveModule(duk_context *ctx)
{
const char *module_id;
const char *parent_id;
module_id = duk_require_string(ctx, 0);
parent_id = duk_require_string(ctx, 1);
duk_push_sprintf(ctx, "%s.js", module_id);
ESP_LOGD(TAG,"resolve_cb: id:'%s', parent-id:'%s', resolve-to:'%s'",
module_id, parent_id, duk_get_string(ctx, -1));
return 1;
}
static duk_ret_t DukOvmsLoadModule(duk_context *ctx)
{
const char *filename;
const char *module_id;
module_id = duk_require_string(ctx, 0);
duk_get_prop_string(ctx, 2, "filename");
filename = duk_require_string(ctx, -1);
if (strncmp(module_id,"int/",4)==0)
{
// Load internal module
std::string name(module_id+4,strlen(module_id)-7);
duktape_registermodule_t* mod = MyDuktape.FindDuktapeModule(name.c_str());
if (mod == NULL)
{
duk_error(ctx, DUK_ERR_TYPE_ERROR, "load_cb: cannot find internal module: %s", module_id);
return 0;
}
else
{
ESP_LOGD(TAG,"load_cb: id:'%s' internally provided %s (%d bytes)", module_id, filename, mod->length);
duk_push_lstring(ctx, mod->start, mod->length);
MyDuktape.NotifyDuktapeModuleLoad(filename);
return 1;
}
}
ESP_LOGD(TAG,"load_cb: id:'%s', filename:'%s'", module_id, filename);
// Resolve the module path
std::string path;
FILE* sf;
if (strncmp(filename,"plugin/",7)==0)
{
path = std::string("/store/plugins/");
path.append(filename+7);
sf = fopen(path.c_str(), "r");
}
else
{
path = std::string("/store/scripts/");
path.append(filename);
sf = fopen(path.c_str(), "r");
if (sf == NULL)
{
path = std::string("/sd/scripts/");
path.append(filename);
sf = fopen(path.c_str(), "r");
}
}
// Go ahead and load the module
if (sf == NULL)
{
duk_error(ctx, DUK_ERR_TYPE_ERROR, "load_cb: cannot find module: %s", module_id);
return 0;
}
else
{
fseek(sf,0,SEEK_END);
long slen = ftell(sf);
fseek(sf,0,SEEK_SET);
char *script = new char[slen+1];
memset(script,0,slen+1);
fread(script,1,slen,sf);
duk_push_string(ctx, script);
delete [] script;
fclose(sf);
ESP_LOGD(TAG,"load_cb: id:'%s' vfs provided %s (%lu bytes)", module_id, filename, slen);
MyDuktape.NotifyDuktapeModuleLoad(filename);
}
return 1;
}
////////////////////////////////////////////////////////////////////////////////
// DuktapeObject
/**
* Construct empty (uncoupled) DuktapeObject
*/
DuktapeObject::DuktapeObject()
{
}
/**
* Construct DuktapeObject coupled to stack object
* Stack: unchanged
*/
DuktapeObject::DuktapeObject(duk_context *ctx, int obj_idx)
{
Couple(ctx, obj_idx);
assert(IsCoupled());
}
/**
* Destruct DuktapeObject
* Note: no JS context here, override Finalize() for JS destructor
*/
DuktapeObject::~DuktapeObject()
{
}
/**
* Ref: increment reference count
*/
void DuktapeObject::Ref()
{
Lock();
m_refcnt++;
// ESP_LOGD(TAG, "DuktapeObject::Ref cnt=%d", m_refcnt);
Unlock();
}
/**
* Unref: decrement reference count, delete self if no references left
* Returns true if instance has been deleted
*/
bool DuktapeObject::Unref()
{
Lock();
assert(m_refcnt > 0);
m_refcnt--;
// ESP_LOGD(TAG, "DuktapeObject::Unref cnt=%d", m_refcnt);
if (m_refcnt == 0)
{
delete this;
return true;
}
Unlock();
return false;
}
/**
* Couple: set hidden instance pointer & finalizer on JS object
* Stack: unchanged
*/
bool DuktapeObject::Couple(duk_context *ctx, int obj_idx)
{
OvmsRecMutexLock lock(&m_mutex);
duk_require_object(ctx, obj_idx);
m_object = duk_get_heapptr(ctx, obj_idx);
if (!m_object) return false;
duk_require_stack(ctx, 3);
duk_push_heapptr(ctx, m_object);
// set instance pointer:
duk_push_pointer(ctx, this);
duk_put_prop_string(ctx, -2, DUK_HIDDEN_SYMBOL("duktapeObject"));
// set finalizer:
duk_push_c_function(ctx, DuktapeFinalizer, 2 /*(object,heapDestruct)*/);
duk_set_finalizer(ctx, -2);
duk_pop(ctx);
Ref();
return true;
}
/**
* Decouple: clear hidden instance pointer & finalizer on JS object
* Stack: unchanged
*/
void DuktapeObject::Decouple(duk_context *ctx)
{
{
OvmsRecMutexLock lock(&m_mutex);
if (!m_object) return;
duk_require_stack(ctx, 3);
duk_push_heapptr(ctx, m_object);
// clear instance pointer:
duk_del_prop_string(ctx, -1, DUK_HIDDEN_SYMBOL("duktapeObject"));
// clear finalizer:
duk_push_undefined(ctx);
duk_set_finalizer(ctx, -2);
duk_pop(ctx);
m_object = NULL;
}
Unref();
}
/**
* GetInstance: get hidden instance pointer from JS object
* Returns null if no instance is attached
* Stack: unchanged
*/
DuktapeObject* DuktapeObject::GetInstance(duk_context *ctx, int obj_idx)
{
DuktapeObject* instance = NULL;
duk_require_stack(ctx, 2);
if (duk_get_prop_string(ctx, obj_idx, DUK_HIDDEN_SYMBOL("duktapeObject")))
instance = (DuktapeObject*)duk_get_pointer(ctx, -1);
duk_pop(ctx);
return instance;
}
/**
* DuktapeFinalizer: Duktape finalizer (destructor) entry
*/
duk_ret_t DuktapeObject::DuktapeFinalizer(duk_context *ctx)
{
DuktapeObject* me = GetInstance(ctx, 0);
bool heapDestruct = duk_opt_boolean(ctx, 1, false);
if (me) me->Finalize(ctx, heapDestruct);
return 0;
}
/**
* Finalize: JS destructor, called by garbage collection
*/
void DuktapeObject::Finalize(duk_context *ctx, bool heapDestruct)
{
// ESP_LOGD(TAG, "DuktapeObject::Finalize heapDestruct=%d", heapDestruct);
Decouple(ctx); // deletes self if no reference left
}
/**
* Push: push coupled JS object onto stack
* Stack: [ ] [ obj ]
*/
duk_idx_t DuktapeObject::Push(duk_context *ctx)
{
OvmsRecMutexLock lock(&m_mutex);
if (!m_object) return DUK_INVALID_INDEX;
duk_require_stack(ctx, 1);
return duk_push_heapptr(ctx, m_object);
}
/**
* Register object with the global stash to prevent garbage collection
* Stack: unchanged
*/
void DuktapeObject::Register(duk_context *ctx)
{
OvmsRecMutexLock lock(&m_mutex);
if (!m_object || m_registered) return;
// ESP_LOGD(TAG, "DuktapeObject::Register @globalStash");
duk_require_stack(ctx, 3);
// get registry:
duk_push_global_stash(ctx);
if (!duk_get_prop_string(ctx, -1, "duktapeObjectRegistry"))
{
// create registry:
duk_pop(ctx);
duk_push_object(ctx);
duk_dup(ctx, -1);
duk_put_prop_string(ctx, -3, "duktapeObjectRegistry");
}
// register using instance address as key:
duk_push_pointer(ctx, this);
duk_push_heapptr(ctx, m_object);
duk_put_prop(ctx, -3);
duk_pop_2(ctx); // registry & global stash
m_registry = NULL;
m_registered = true;
}
/**
* Register with a specific registry object on the stack
* Note: you need to ensure the object is stable until deregistration!
* Stack: unchanged
*/
void DuktapeObject::Register(duk_context *ctx, int obj_idx)
{
OvmsRecMutexLock lock(&m_mutex);
if (!m_object || m_registered) return;
// ESP_LOGD(TAG, "DuktapeObject::Register @object");
duk_require_object(ctx, obj_idx);
duk_require_stack(ctx, 3);
// register using instance address as key:
duk_dup(ctx, obj_idx);
duk_push_pointer(ctx, this);
duk_push_heapptr(ctx, m_object);
duk_put_prop(ctx, -3);
duk_pop(ctx);
m_registry = duk_get_heapptr(ctx, obj_idx);
m_registered = true;
}
/**
* Deregister:
* Stack: unchanged
*/
void DuktapeObject::Deregister(duk_context *ctx)
{
OvmsRecMutexLock lock(&m_mutex);
if (!m_object || !m_registered) return;
// ESP_LOGD(TAG, "DuktapeObject::Deregister");
duk_require_stack(ctx, 3);
if (m_registry)
{
duk_push_heapptr(ctx, m_registry);
duk_push_pointer(ctx, this);
duk_del_prop(ctx, -2);
duk_pop(ctx); // registry
}
else
{
duk_push_global_stash(ctx);
if (duk_get_prop_string(ctx, -1, "duktapeObjectRegistry"))
{
duk_push_pointer(ctx, this);
duk_del_prop(ctx, -2);
}
duk_pop_2(ctx); // registry & global stash
}
m_registry = NULL;
m_registered = false;
}
/**
* RequestCallback: request object method call by Duktape context
*/
void DuktapeObject::RequestCallback(const char* method, void* data /*=NULL*/)
{
if (MyDuktape.DukTapeAvailable())
{
Ref();
MyDuktape.DuktapeRequestCallback(this, method, data);
}
else
{
ESP_LOGE(TAG, "DuktapeObject::RequestCallback(%s) failed: Duktape not available", method);
}
}
/**
* DuktapeCallback: call object method in Duktape context
*/
duk_ret_t DuktapeObject::DuktapeCallback(duk_context *ctx, duktape_queue_t &msg)
{
duk_ret_t res = -1;
if (ctx)
res = CallMethod(ctx, msg.body.dt_callback.method, msg.body.dt_callback.data);
else
ESP_LOGE(TAG, "DuktapeObject::DuktapeCallback(%s) failed: Duktape shutdown", msg.body.dt_callback.method);
Unref();
return res;
}
/**
* CallMethod: execute object method in Duktape context
* Default implementation: no args, no return values, no error processing
*/
duk_ret_t DuktapeObject::CallMethod(duk_context *ctx, const char* method, void* data /*=NULL*/)
{
if (!ctx)
{
RequestCallback(method, data);
return 0;
}
OvmsRecMutexLock lock(&m_mutex);
if (!IsCoupled()) return 0;
int entry_top = duk_get_top(ctx);
Push(ctx);
duk_push_string(ctx, method);
if (duk_pcall_prop(ctx, -2, 0) != 0)
{
DukOvmsErrorHandler(ctx, -1);
}
// discard return values if any + this:
duk_pop_n(ctx, duk_get_top(ctx) - entry_top);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// DuktapeObjectRegistration
//
// An object to record a Duktape object registration for OVMS
DuktapeObjectRegistration::DuktapeObjectRegistration(const char* name)
{
m_name = name;
}
DuktapeObjectRegistration::~DuktapeObjectRegistration()
{
}
const char* DuktapeObjectRegistration::GetName()
{
return m_name;
}
void DuktapeObjectRegistration::RegisterDuktapeFunction(duk_c_function func, duk_idx_t nargs, const char* name)
{
duktape_registerfunction_t* fn = new duktape_registerfunction_t;
fn->func = func;
fn->nargs = nargs;
m_fnmap[name] = fn;
}
void DuktapeObjectRegistration::RegisterWithDuktape(duk_context* ctx)
{
duk_push_object(ctx);
DuktapeFunctionMap::iterator itm=m_fnmap.begin();
while (itm!=m_fnmap.end())
{
const char* name = itm->first;
duktape_registerfunction_t* fn = itm->second;
ESP_LOGD(TAG,"Duktape: Pre-Registered object %s function %s",m_name,name);
duk_push_c_function(ctx, fn->func, fn->nargs);
duk_push_string(ctx, "name");
duk_push_string(ctx, name);
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE); /* Improve stacktraces by displaying function name */
duk_put_prop_string(ctx, -2, name);
++itm;
}
duk_put_global_string(ctx, m_name);
}
////////////////////////////////////////////////////////////////////////////////
// DuktapeConsoleCommand
DuktapeConsoleCommand::DuktapeConsoleCommand(duk_context *ctx, int obj_idx, OvmsCommand* cmd, const char* module)
: DuktapeObject(ctx, obj_idx)
{
m_cmd = cmd;
m_module = std::string(module);
}
DuktapeConsoleCommand::~DuktapeConsoleCommand()
{
}
////////////////////////////////////////////////////////////////////////////////
// OvmsDuktape
//
// This is the main OVMS Duktape registry and singleton utiltiy object
OvmsDuktape::OvmsDuktape()
{
ESP_LOGI(TAG, "Initialising DUKTAPE Registry (1000)");
m_dukctx = NULL;
m_duktaskid = NULL;
m_duktaskqueue = NULL;
// Register standard modules...
extern const char mod_pubsub_js_start[] asm("_binary_pubsub_js_start");
extern const char mod_pubsub_js_end[] asm("_binary_pubsub_js_end");
RegisterDuktapeModule(mod_pubsub_js_start, mod_pubsub_js_end - mod_pubsub_js_start, "PubSub");
extern const char mod_json_js_start[] asm("_binary_json_js_start");
extern const char mod_json_js_end[] asm("_binary_json_js_end");
RegisterDuktapeModule(mod_json_js_start, mod_json_js_end - mod_json_js_start, "JSON");
// Start the DukTape task...
m_duktaskqueue = xQueueCreate(CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_QUEUE_SIZE,sizeof(duktape_queue_t));
xTaskCreatePinnedToCore(DukTapeLaunchTask, "OVMS DukTape",
CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_STACK, (void*)this,
CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_PRIORITY, &m_duktaskid, CORE(1));
AddTaskToMap(m_duktaskid);
}
OvmsDuktape::~OvmsDuktape()
{
duk_destroy_heap(m_dukctx);
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
if (umm_memory != NULL)
{
free(umm_memory);
umm_memory = NULL;
}
#endif
m_dukctx = NULL;
}
void OvmsDuktape::RegisterDuktapeFunction(duk_c_function func, duk_idx_t nargs, const char* name)
{
duktape_registerfunction_t* fn = new duktape_registerfunction_t;
fn->func = func;
fn->nargs = nargs;
m_fnmap[name] = fn;
}
void OvmsDuktape::RegisterDuktapeModule(const char* start, size_t length, const char* name)
{
duktape_registermodule_t* mn = new duktape_registermodule_t;
mn->start = start;
mn->length = length;
m_modmap[name] = mn;
}
duktape_registermodule_t* OvmsDuktape::FindDuktapeModule(const char* name)
{
auto mod = m_modmap.find(name);
if (mod == m_modmap.end())
{ return NULL; }
else
{ return mod->second; }
}
void OvmsDuktape::RegisterDuktapeObject(DuktapeObjectRegistration* ob)
{
m_obmap[ob->GetName()] = ob;
}
void OvmsDuktape::AutoInitDuktape()
{
if (MyConfig.GetParamValueBool("auto", "scripting", true))
{
duktape_queue_t dmsg;
memset(&dmsg, 0, sizeof(dmsg));
dmsg.type = DUKTAPE_autoinit;
DuktapeDispatch(&dmsg);
}
}
void OvmsDuktape::EventScript(std::string event, void* data)
{
if (!m_dukctx) return;
// dispatch event to PubSub component:
duktape_queue_t dmsg;
memset(&dmsg, 0, sizeof(dmsg));
dmsg.type = DUKTAPE_event;
dmsg.body.dt_event.name = strdup(event.c_str());
dmsg.body.dt_event.data = NULL; // data unused, may also be invalid in async script execution
if (!DuktapeDispatch(&dmsg, 0))
{
ESP_LOGE(TAG, "EventScript: event '%s' lost (queue overflow)", event.c_str());
free((void*)dmsg.body.dt_event.name);
}
else
{
// event processing delayed?
int qwait = uxQueueMessagesWaiting(m_duktaskqueue);
if (qwait > 10)
{
ESP_LOGW(TAG, "EventScript: event '%s' delayed, queued at position %d/%d", event.c_str(),
qwait, CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_QUEUE_SIZE);
}
}
if (event == "ticker.60")
{
// request garbage collection once per minute:
DuktapeCompact(false);
}
}
bool OvmsDuktape::DuktapeDispatch(duktape_queue_t* msg, TickType_t queuewait /*=portMAX_DELAY*/)
{
msg->waitcompletion = NULL;
if (xQueueSend(m_duktaskqueue, msg, queuewait) != pdPASS)
{
ESP_LOGW(TAG, "DuktapeDispatch: msg type %u lost, queue full", msg->type);
return false;
}
else
{
return true;
}
}
void OvmsDuktape::DuktapeDispatchWait(duktape_queue_t* msg)
{
msg->waitcompletion = xSemaphoreCreateBinary();
xQueueSend(m_duktaskqueue, msg, portMAX_DELAY);
xSemaphoreTake(msg->waitcompletion, portMAX_DELAY);
vSemaphoreDelete(msg->waitcompletion);
msg->waitcompletion = NULL;
}
void OvmsDuktape::DuktapeEvalNoResult(const char* text, OvmsWriter* writer, const char* filename /*=NULL*/)
{
duktape_queue_t dmsg;
memset(&dmsg, 0, sizeof(dmsg));
dmsg.type = DUKTAPE_evalnoresult;
dmsg.writer = writer;
dmsg.body.dt_evalnoresult.text = text;
dmsg.body.dt_evalnoresult.filename = filename;
DuktapeDispatchWait(&dmsg);
}
float OvmsDuktape::DuktapeEvalFloatResult(const char* text, OvmsWriter* writer)
{
float result = 0;
duktape_queue_t dmsg;
memset(&dmsg, 0, sizeof(dmsg));
dmsg.type = DUKTAPE_evalfloatresult;
dmsg.writer = writer;
dmsg.body.dt_evalfloatresult.text = text;
dmsg.body.dt_evalfloatresult.result = &result;
DuktapeDispatchWait(&dmsg);
return result;
}
int OvmsDuktape::DuktapeEvalIntResult(const char* text, OvmsWriter* writer)
{
int result = 0;
duktape_queue_t dmsg;
memset(&dmsg, 0, sizeof(dmsg));
dmsg.type = DUKTAPE_evalintresult;
dmsg.writer = writer;
dmsg.body.dt_evalintresult.text = text;
dmsg.body.dt_evalintresult.result = &result;
DuktapeDispatchWait(&dmsg);
return result;
}
void OvmsDuktape::DuktapeReload()
{
duktape_queue_t dmsg;
memset(&dmsg, 0, sizeof(dmsg));
dmsg.type = DUKTAPE_reload;
DuktapeDispatchWait(&dmsg);
}
void OvmsDuktape::DuktapeCompact(bool wait /*=true*/)
{
duktape_queue_t dmsg;
memset(&dmsg, 0, sizeof(dmsg));
dmsg.type = DUKTAPE_compact;
if (wait)
DuktapeDispatchWait(&dmsg);
else
DuktapeDispatch(&dmsg, 0);
}
void OvmsDuktape::DuktapeRequestCallback(DuktapeObject* instance, const char* method, void* data)
{
duktape_queue_t dmsg;
memset(&dmsg, 0, sizeof(dmsg));
dmsg.type = DUKTAPE_callback;
dmsg.body.dt_callback.instance = instance;
dmsg.body.dt_callback.method = method;
dmsg.body.dt_callback.data = data;
DuktapeDispatch(&dmsg);
}
OvmsWriter* OvmsDuktape::GetDuktapeWriter()
{
return duktapewriter;
}
void OvmsDuktape::DukGetCallInfo(duk_context *ctx, std::string *filename, int *linenumber, std::string *function)
{
duk_require_stack(ctx, 3);
for (int i=-2; ; i--)
{
*filename = "";
*function = "";
duk_inspect_callstack_entry(ctx, i);
if (duk_is_undefined(ctx, -1))
{
duk_pop(ctx);
break;
}
duk_get_prop_string(ctx, -1, "lineNumber");
*linenumber = duk_get_number_default(ctx, -1, 0);
duk_pop(ctx);
if (duk_get_prop_string(ctx, -1, "function"))
{
duk_get_prop_string(ctx, -1, "fileName");
*filename = duk_get_string_default(ctx, -1, "");
duk_pop(ctx);
duk_get_prop_string(ctx, -1, "name");
*function = duk_get_string_default(ctx, -1, "");
duk_pop(ctx);
}
duk_pop_2(ctx);
// skip internal modules:
if (!startsWith(*filename, "int/"))
break;
}
}
void OvmsDuktape::NotifyDuktapeModuleLoad(const char* filename)
{
ESP_LOGD(TAG,"Duktape: module load: %s",filename);
// We don't need to do anythign special with this
}
void OvmsDuktape::NotifyDuktapeModuleUnload(const char* filename)
{
ESP_LOGD(TAG,"Duktape: module unload: %s",filename);
// We need to go through the registered duktape commands and
// unregister any belonging to the specified module
for (auto it = m_cmdmap.begin(); it != m_cmdmap.end(); ++it)
{
OvmsCommand* cmd = it->first;
DuktapeConsoleCommand* dcc = it->second;
if (dcc->m_module.compare(filename) == 0)
{
ESP_LOGD(TAG,"Duktape: unregister command %s/%s",
cmd->GetParent()->GetName(), cmd->GetName());
delete dcc;
cmd->GetParent()->UnregisterCommand(cmd->GetName());
delete cmd;
m_cmdmap.erase(it);
}
}
}
void OvmsDuktape::NotifyDuktapeModuleUnloadAll()
{
ESP_LOGD(TAG,"Duktape: module unload all");
// We need to unregister all registered duktape commands
// (for all modules)
for (auto it = m_cmdmap.begin(); it != m_cmdmap.end(); ++it)
{
OvmsCommand* cmd = it->first;
DuktapeConsoleCommand* dcc = it->second;
ESP_LOGD(TAG,"Duktape: unregister command %s/%s",
cmd->GetParent()->GetName(), cmd->GetName());
delete dcc;
cmd->GetParent()->UnregisterCommand(cmd->GetName());
delete cmd;
}
m_cmdmap.clear();
}
void DukOvmsCommandRegisterRun(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
ESP_LOGD(TAG, "DukOvmsCommandRegisterRun(%s)",cmd->GetName());
auto it = MyDuktape.m_cmdmap.find(cmd);
if (it == MyDuktape.m_cmdmap.end())
{
ESP_LOGE(TAG, "Command '%s' cannot be found in registry",cmd->GetName());
return;
}
else
{
// TODO
// DuktapeConsoleCommand* dcc = it->second;
// Perform the callback
}
}
bool OvmsDuktape::RegisterDuktapeConsoleCommand(
duk_context *ctx, int obj_idx,
const char* filename,
const char* parent,
const char* name, const char* title,
const char *usage, int min, int max)
{
ESP_LOGD(TAG,"Duktape: register console command '%s'",name);
// We need to register the specified console command, to
// the specified module
OvmsCommand* cmd;
if (*parent == 0)
{
cmd = MyCommandApp.RegisterCommand(
name, title, DukOvmsCommandRegisterRun, usage, min, max);
}
else
{
OvmsCommand* pcmd = MyCommandApp.FindCommandFullName(parent);
if (pcmd == NULL)
{
ESP_LOGE(TAG,"Duktape: Script %s trying to register unknown command %s/%s",
filename, parent, name);
return false;
}
cmd = pcmd->RegisterCommand(
name, title, DukOvmsCommandRegisterRun, usage, min, max);
}
if (cmd != NULL)
{
DuktapeConsoleCommand *dcc = new DuktapeConsoleCommand(ctx, obj_idx, cmd, filename);
m_cmdmap[cmd] = dcc;
return true;
}
else
{
ESP_LOGE(TAG,"Duktape: Could not register command %s/%s for script %s",
parent, name, filename);
return false;
}
}
void OvmsDuktape::DukTapeInit()
{
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
// Allocate dedicated UMM heap space:
int memsize = MyConfig.GetParamValueInt("module", "duktape.heapsize",
CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM_DEFAULTSIZE) * 1024;
if (memsize <= 0)
memsize = 512 * 1024;
else if (memsize > UMM_MAX_BLOCKS * UMM_BLOCK_BODY_SIZE)
memsize = UMM_MAX_BLOCKS * UMM_BLOCK_BODY_SIZE;
ESP_LOGI(TAG, "Duktape: Creating heap (size: %u bytes)", memsize);
umm_memory = ExternalRamMalloc(memsize);
if (!umm_memory)
{
ESP_LOGE(TAG, "Duktape: unable to allocate %u bytes for the heap", memsize);
return;
}
umm_init_heap(umm_memory, memsize);
#else
ESP_LOGI(TAG, "Duktape: Creating heap");
#endif
m_dukctx = duk_create_heap(DukOvmsAlloc,
DukOvmsRealloc,
DukOvmsFree,
this,
DukOvmsFatalHandler);
ESP_LOGI(TAG,"Duktape: Initialising module system");
duk_push_object(m_dukctx);
duk_push_c_function(m_dukctx, DukOvmsResolveModule, DUK_VARARGS);
duk_put_prop_string(m_dukctx, -2, "resolve");
duk_push_c_function(m_dukctx, DukOvmsLoadModule, DUK_VARARGS);
duk_put_prop_string(m_dukctx, -2, "load");
duk_module_node_init(m_dukctx);
if (m_fnmap.size() > 0)
{
// We have some functions to register...
DuktapeFunctionMap::iterator itm=m_fnmap.begin();
while (itm!=m_fnmap.end())
{
const char* name = itm->first;
duktape_registerfunction_t* fn = itm->second;
ESP_LOGD(TAG,"Duktape: Pre-Registered function %s",name);
duk_push_c_function(m_dukctx, fn->func, fn->nargs);
duk_put_global_string(m_dukctx, name);
++itm;
}
}
if (m_obmap.size() > 0)
{
// We have some objects to register...
DuktapeObjectMap::iterator itm=m_obmap.begin();
while (itm!=m_obmap.end())
{
const char* name = itm->first;
DuktapeObjectRegistration* ob = itm->second;
ESP_LOGD(TAG,"Duktape: Pre-Registered object %s",name);
ob->RegisterWithDuktape(m_dukctx);
++itm;
}
}
// Load registered modules
if (m_modmap.size() > 0)
{
// We have some modules to register...
DuktapeModuleMap::iterator itm=m_modmap.begin();
while (itm!=m_modmap.end())
{
const char* name = itm->first;
ESP_LOGD(TAG,"Duktape: Pre-Registered module %s",name);
std::string loadfn("(function(){");
loadfn.append(name);
loadfn.append("=require(\"int/");
loadfn.append(name);
loadfn.append("\");})();");
duk_push_string(m_dukctx, loadfn.c_str());
if (duk_peval(m_dukctx) != 0)
{
ESP_LOGE(TAG,"Duktape: %s",duk_safe_to_string(m_dukctx, -1));
}
duk_pop(m_dukctx);
++itm;
}
}
#ifdef CONFIG_OVMS_COMP_PLUGINS
// Plugins
MyPluginStore.LoadEnabledModules(EL_MODULE);
#endif // #ifdef CONFIG_OVMS_COMP_PLUGINS
// ovmsmain
FILE* sf = fopen("/store/scripts/ovmsmain.js", "r");
if (sf != NULL)
{
fseek(sf,0,SEEK_END);
long slen = ftell(sf);
fseek(sf,0,SEEK_SET);
char *script = new char[slen+1];
memset(script,0,slen+1);
fread(script,1,slen,sf);
duk_push_string(m_dukctx, script);
delete [] script;
fclose(sf);
ESP_LOGI(TAG,"Duktape: Executing ovmsmain.js");
NotifyDuktapeModuleLoad("ovmsmain.js");
duk_module_node_peval_main(m_dukctx, "ovmsmain.js");
NotifyDuktapeModuleUnload("ovmsmain.js");
}
}
void OvmsDuktape::DukTapeTask()
{
duktape_queue_t msg;
ESP_LOGI(TAG,"Duktape: Scripting task is running");
esp_task_wdt_add(NULL); // WATCHDOG is active for this task
while(1)
{
if (xQueueReceive(m_duktaskqueue, &msg, pdMS_TO_TICKS(5000))==pdTRUE)
{
esp_task_wdt_reset(); // Reset WATCHDOG timer for this task
duktapewriter = msg.writer;
switch(msg.type)
{
case DUKTAPE_reload:
{
// Reload DUKTAPE engine
NotifyDuktapeModuleUnloadAll();
if (m_dukctx != NULL)
{
ESP_LOGI(TAG,"Duktape: Clearing existing context");
duk_destroy_heap(m_dukctx);
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
if (umm_memory != NULL)
{
free(umm_memory);
umm_memory = NULL;
}
#endif
m_dukctx = NULL;
}
DukTapeInit();
}
break;
case DUKTAPE_compact:
{
// Compact DUKTAPE memory
if (m_dukctx != NULL)
{
ESP_LOGV(TAG,"Duktape: Compacting DukTape memory");
uint32_t ts = esp_log_timestamp();
duk_gc(m_dukctx, 0);
duk_gc(m_dukctx, 0);
ESP_LOGD(TAG, "Duktape: Compacting DukTape memory done in %u ms", esp_log_timestamp()-ts);
}
}
break;
case DUKTAPE_event:
{
// Event
if (m_dukctx != NULL)
{
// Deliver the event to DUKTAPE
uint32_t ts = esp_log_timestamp();
duk_require_stack(m_dukctx, 5);
duk_get_global_string(m_dukctx, "PubSub");
duk_get_prop_string(m_dukctx, -1, "publish");
duk_dup(m_dukctx, -2); /* this binding = process */
duk_push_string(m_dukctx, msg.body.dt_event.name);
duk_push_string(m_dukctx, "");
if (duk_pcall_method(m_dukctx, 2) != 0)
{
DukOvmsErrorHandler(m_dukctx, -1);
}
duk_pop_2(m_dukctx);
ts = esp_log_timestamp() - ts;
if (ts > 1000)
ESP_LOGW(TAG, "Duktape: event handling for '%s' took %u ms", msg.body.dt_event.name, ts);
}
}
free((void*)msg.body.dt_event.name);
break;
case DUKTAPE_autoinit:
{
// Auto init
DukTapeInit();
}
break;
case DUKTAPE_evalnoresult:
if (m_dukctx != NULL)
{
// Execute script text (without result)
const char* filename = msg.body.dt_evalnoresult.filename;
if (!filename) filename = "eval";
duk_push_string(m_dukctx, msg.body.dt_evalnoresult.text);
duk_push_string(m_dukctx, filename);
if (duk_pcompile(m_dukctx, DUK_COMPILE_EVAL) != 0 || duk_pcall(m_dukctx, 0) != 0)
{
DukOvmsErrorHandler(m_dukctx, -1, msg.writer, filename);
}
duk_pop(m_dukctx);
}
else
{
if (msg.writer)
msg.writer->puts("ERROR: Duktape not started");
else
ESP_LOGE(TAG, "Duktape not started");
}
break;
case DUKTAPE_evalfloatresult:
if (m_dukctx != NULL)
{
// Execute script text (float result)
duk_push_string(m_dukctx, msg.body.dt_evalfloatresult.text);
if (duk_peval(m_dukctx) != 0)
{
DukOvmsErrorHandler(m_dukctx, -1, msg.writer);
*msg.body.dt_evalfloatresult.result = 0;
}
else
{
*msg.body.dt_evalfloatresult.result = (float)duk_get_number(m_dukctx,-1);
}
duk_pop(m_dukctx);
}
else
{
if (msg.writer)
msg.writer->puts("ERROR: Duktape not started");
else
ESP_LOGE(TAG, "Duktape not started");
*msg.body.dt_evalfloatresult.result = 0;
}
break;
case DUKTAPE_evalintresult:
if (m_dukctx != NULL)
{
// Execute script text (int result)
duk_push_string(m_dukctx, msg.body.dt_evalintresult.text);
if (duk_peval(m_dukctx) != 0)
{
DukOvmsErrorHandler(m_dukctx, -1, msg.writer);
*msg.body.dt_evalintresult.result = 0;
}
else
{
*msg.body.dt_evalintresult.result = (int)duk_get_int(m_dukctx,-1);
}
duk_pop(m_dukctx);
}
else
{
if (msg.writer)
msg.writer->puts("ERROR: Duktape not started");
else
ESP_LOGE(TAG, "Duktape not started");
*msg.body.dt_evalintresult.result = 0;
}
break;
case DUKTAPE_callback:
{
// DuktapeObject callback (without result)
DuktapeObject* dto = msg.body.dt_callback.instance;
dto->DuktapeCallback(m_dukctx, msg);
}
break;
default:
ESP_LOGE(TAG,"Duktape: Unrecognised msg type 0x%04x",msg.type);
break;
}
duktapewriter = NULL;
if (msg.waitcompletion)
{
// Signal the completion...
xSemaphoreGive(msg.waitcompletion);
}
}
esp_task_wdt_reset(); // Reset WATCHDOG timer for this task
}
}