TinyEngine本地扩展对象开发指南 - Shaofa/AliOS-Things-Certification-Manual GitHub Wiki

本地扩展对象(Addon)开发介绍

使用头文件: #include <be_jse_module.h>, 参考例程 module_timers.cmodule_mqtt.c

JavaScript的本地扩展对象简称Addon,一般是用C语言实现的模块,在JS程序可以调用该对象的方法或属性;

当前__Addon__开发支持全局对象的扩展, 支持静态自定义对象扩展,但不支持new一个自定义对象

编写自定义扩展Addon需要了解JSE的词法分析、语法分析处理、符号表处理, 在JSE中每一个变量、函数都对应一个或多个be_jse_symbol_t符号表

举例说明: 处理执行var num = 1 + 2语句, 只有num会保存在符号表中, 1 + 2在执行时会一边解析一边计算,将结果3保存在num的值中,也是保存在符号表中,这个语法块分析处理的结果占用两个符号表be_jse_symbol_t,一个保存num的名称,一个保存num的值3

Addon类型一, 全局的方法或属性的扩展

module_timers.c 为例作介绍

全局函数: setTimeout(callback, delay);

在语法分析执行时,JS程序中遇到这种语法块setTimeout(xxx),分析到setTimeout这个token时,会进入timers_module_handle_cb处理,并匹配到setTimeout关键词,并进入setup_timer(0)继续做语法分析处理,返回一个be_jse_symbol_t*对象.

be_jse_symbol_t *timers_module_handle_cb(be_jse_vm_ctx_t *execInfo, be_jse_symbol_t *var, const char *name)
{
    if (!strcmp(name, "setTimeout"))
        return setup_timer(0);
    if (!strcmp(name, "clearTimeout"))
        return clear_timer();
    if (!strcmp(name, "setInterval"))
        return setup_timer(1);
    if (!strcmp(name, "clearInterval"))
        return clear_timer();

    return BE_JSE_FUNC_UNHANDLED;
}

setTimeout该全局对象方法的语法处理函数

#define MAGIC  0x55ff0055
struct timer_wrap {
    int magic;
    be_jse_symbol_t *cbv;
    long ms;
    int repeat;
};
typedef struct timer_wrap timer_wrap_t;

static void timer_action(void *arg);

static void call_action(void *arg)
{
    be_jse_symbol_t *func = (be_jse_symbol_t*)arg;

    be_jse_execute_func(bone_engine_get_executor(), func, 0, NULL);

    DEC_SYMBL_REF(func);

}


static void timer_action(void *arg)
{
    LOGD("MODULE_TIMERS"," 1 aos_task_name=%s \r\n", aos_task_name());

    timer_wrap_t *t = (timer_wrap_t *)arg;

    LOGD("MODULE_TIMERS","JS handle =%d \n", t);

    if( t->magic == MAGIC ) {

        be_jse_symbol_t *func = get_symbol_value(t->cbv);
        if (symbol_is_function(func)) {
            INC_SYMBL_REF(func);
            // 需要考虑在callback中clearInterval/clearTimeout自身
            aos_schedule_call(call_action, func);
        }
        symbol_unlock(func);
        if (t->repeat) {
            aos_post_delayed_action(t->ms, timer_action, t);
        } else {
            symbol_unlock(t->cbv);
            t->magic = 0;
            aos_free(t);
            LOGI("MODULE_TIMERS","aos_free(%p) \n", t);
        }
    } else {
        LOGW("MODULE_TIMERS","alread free \n");
    }

    LOGD("MODULE_TIMERS"," 2 aos_task_name=%s \r\n", aos_task_name());

}

static be_jse_symbol_t *setup_timer(int repeat)
{
    be_jse_symbol_t *cbv;
    be_jse_symbol_t *msv;
    be_jse_handle_function(0, &cbv, &msv, NULL, NULL);

    be_jse_symbol_t *func = get_symbol_value(cbv);
    if( !func || !msv || (!symbol_is_function(func))) {
        LOGE("MODULE_TIMERS","Bad parameter \n");
        symbol_unlock(func);
        symbol_unlock(cbv);
        symbol_unlock(msv);
        return new_symbol(BE_SYM_NULL);
    }
    symbol_unlock(func);

    long ms = get_symbol_value_int(msv);
    symbol_unlock(msv);

    timer_wrap_t *t = aos_malloc(sizeof(*t));
    t->magic = MAGIC;
    t->cbv = cbv;
    t->ms = ms;
    t->repeat = repeat;
    aos_post_delayed_action(ms, timer_action, t);

    LOGD("MODULE_TIMERS","JS handle =%d \n", t);

    return new_int_symbol((be_jse_int_t)t);
}

Addon类型二, 自定义对象的属性或方法的扩展

module_mqtt.c为例作介绍

MQTT对象扩展一个方法、一个属性, API如下

  • MQTT.VERSION
  • MQTT.sign(obj)

注册对象扩展函数,在语法分析时遇到MQTT这个token就进入该module_handle_cb函数做语法处理

#define JS_MQTT_TAG  "MQTT"
be_jse_module_load(JS_MQTT_TAG, module_handle_cb);

MQTT对象的语法处理函数

static be_jse_symbol_t* module_handle_cb(be_jse_vm_ctx_t* execInfo, be_jse_symbol_t* var, const char* name)
{
    int ret;
    be_jse_symbol_t* arg0 = NULL;
    be_jse_symbol_t* arg1 = NULL;

    char productKey[16];
    char deviceName[32];
    char deviceSecret[48];

    // 属性
    if (strcmp(name, "VERSION") == 0) {
        lexer_token_cmp(execInfo->lex, BE_TOKEN_ID);
        return new_str_symbol("0.0.1");
    }

    // 方法
    if(strcmp(name, "sign") == 0 ){
        be_jse_handle_function(0, &arg0, NULL, NULL, NULL);
        if (symbol_is_object(arg0)) {

                be_jse_symbol_t* productKeySymbol = unlock_symbol_value(lookup_named_child_symbol(get_symbol_node_id(arg0), "productKey", false));
                be_jse_symbol_t* deviceNameSymbol = unlock_symbol_value(lookup_named_child_symbol(get_symbol_node_id(arg0), "deviceName", false));
                be_jse_symbol_t* deviceSecretSymbol = unlock_symbol_value(lookup_named_child_symbol(get_symbol_node_id(arg0), "deviceSecret", false));
                symbol_unlock(arg0);


                if (productKeySymbol == NULL
                    || deviceNameSymbol == NULL
                    || deviceSecretSymbol == NULL
                ) {

                    LOGW(JS_MQTT_TAG, "参数错误");
                    return new_str_symbol(NULL);
                }


                symbol_to_str(productKeySymbol, productKey, sizeof(productKey));
                symbol_to_str(deviceNameSymbol, deviceName, sizeof(deviceName));
                symbol_to_str(deviceSecretSymbol, deviceSecret, sizeof(deviceSecret));


                symbol_unlock(productKeySymbol);
                symbol_unlock(deviceNameSymbol);
                symbol_unlock(deviceSecretSymbol);

                unsigned char content[256] = {0};
                sprintf((char*)content, "clientId%sdeviceName%sproductKey%s", deviceName, deviceName, productKey);

                mbedtls_sha1_context sha1_ctx;
                unsigned char k_ipad[64] = {0};
                unsigned char k_opad[64] = {0};
                unsigned char out[20];

                memcpy(k_ipad, deviceSecret, strlen(deviceSecret));
                memcpy(k_opad, deviceSecret, strlen(deviceSecret));

                for (int i = 0; i < sizeof(k_ipad); i++) {
                    k_ipad[i] ^= 0x36;
                    k_opad[i] ^= 0x5c;
                }

                mbedtls_sha1_init(&sha1_ctx);
                mbedtls_sha1_starts(&sha1_ctx);
                mbedtls_sha1_update(&sha1_ctx, k_ipad, sizeof(k_ipad));
                mbedtls_sha1_update(&sha1_ctx, content, strlen((char*)content));
                mbedtls_sha1_finish(&sha1_ctx, out);

                mbedtls_sha1_init(&sha1_ctx);
                mbedtls_sha1_starts(&sha1_ctx);
                mbedtls_sha1_update(&sha1_ctx, k_opad, sizeof(k_opad));
                mbedtls_sha1_update(&sha1_ctx, out, sizeof(out));
                mbedtls_sha1_finish(&sha1_ctx, out);
                mbedtls_sha1_free(&sha1_ctx);

                char sign[41] = {0};
                for (int i=0; i<sizeof(out); ++i) {
                    unsigned char byte[2] = {0};
                    byte[0] = out[i] / 16;
                    byte[1] = out[i] % 16;

                    for(int j=0; j<2; ++j) {
                        if(byte[j] >= 0 && byte[j] <= 9)
                            sign[2 * i + j] = '0' + byte[j];
                        else
                            sign[2 * i + j] = 'a' + byte[j] - 10;
                    }
                }

                return new_str_symbol(sign);
            }
    }
    ... ...
}

回调或事件输出介绍

名词解释

  • 回调函数(callback)与事件处理(event) 回调和事件处理程序本质上并无区别,只是在不同情况下的不同叫法。

    回调函数

    • 程序(JS)执行某个API方法操作时不阻塞,在另一个task中执行具体工作,JSE顺序分析执行接下的语法块,在具体工作完成之后或指定时间到达之后再来执行该回调函数,典型应用如下:
      • MQTT.start(config, callback), MQTT.start函数会立即返回,不会阻塞主任务,MQTT的启动、连接等实际工作在另一个task中执行,执行完成之后再来执行callback函数
      • setTimeout(callback, delay), 在定时器到期后执行callback函数

    事件处理:

    • 在JS主任务中,事件循环处理中收到其它task的发来的事件请求后执行指定的事件回调函数
    • MQTT.subscribe(topic, callback), MQTT.subscribe函数会立即返回,不会阻塞主任务,在MQTT任务中收到订阅的topic之后会发事件请求给主任务,主任务收到该事件之后调用callback函数
  • 符号表(be_jse_symbol_t) JSE解析JavaScript时把所有JS对象都会记录保持在符号表中,每个符号表的两中重要标识,一个是refs引用计数,与作用域有关,还有一个是locks锁定计数,一般与函数使用有关,在函数中使用时+1,不再使用时-1,只有当引用计数和锁定计数都为0时,回收该对象,释放该符号表。 符号表的基本类型主要有: NULL字符串数组对象函数,整数浮点数对象名称本地扩展对象 举例var a=5说明, 在符号表数组中,a是对象名称,5一个整数类型的值。

开发详解

(具体实现可以参考 module_mqtt.c、module_timers.c 等)

  • 如何产生异步调用(异步编程模型)?

耗时操作应该在一个new task中运行,结果通过aos_schedule_call函数在action中输出, 通过JS callback函数输出,(C调用javascript函数)在JavaScript主任务上下文中执行

static void call_action(void *arg)
{
    be_jse_symbol_t *func = (be_jse_symbol_t*)arg;
    be_jse_execute_func(bone_engine_get_executor(), func, 0, NULL);
    DEC_SYMBL_REF(func);
}

  • C 如何调用JavaScript的函数

C调用Javascript函数,一般采用 be_jse_execute_func API函数,be_jse_execute_func 函数原始如下:

bool be_jse_execute_func(be_jse_executor_ctx_t *executor, be_jse_symbol_t *func, int argCount, be_jse_symbol_t **argPtr);

注意事件 1.事件通知: 如何需要多次事件通知, func不能释放,可以参考module_timers.c 2.BE_ASYNC_S* async 使用完之后需要释放

如何构造数组?

示例: ['abcd', 'abcd', 'abcd','abcd','abcd']

int i;
be_jse_symbol_t *arr = new_symbol(BE_SYM_ARRAY);
for (i=0; i<5; i++) {
    be_jse_symbol_t *val = new_str_symbol("abcd");
    be_jse_symbol_t *idx = new_named_symbol(new_int_symbol(i), val);
    symbol_unlock(val);
    add_symbol_node(arr, idx);
    symbol_unlock(idx);
}

如何构造对象?

示例:{"name": "IoT"}

be_jse_symbol_t *obj = new_symbol(BE_SYM_OBJECT);
be_jse_symbol_t *name = new_str_symbol("IoT");
be_jse_symbol_t *val = add_symbol_node_name(obj, name, "name");
symbol_unlock(name);
symbol_unlock(val);
  • 根据JSON字符串构造对象或数组 be_jse_symbol_t *new_json_symbol(char* json_str, size_t json_str_len)

JSE重点API介绍

  • 符号相关函数 JSE中符号表类型be_jse_symbol_type_e
// 符号表类型判断
bool symbol_is_root(const be_jse_symbol_t *v);
bool symbol_is_int(const be_jse_symbol_t *s);
bool symbol_is_float(const be_jse_symbol_t *s);
bool symbol_is_string(const be_jse_symbol_t *s);
bool symbol_is_string_ext(const be_jse_symbol_t *s);
bool symbol_is_number(const be_jse_symbol_t *s);
bool symbol_is_function(const be_jse_symbol_t *s);
bool symbol_is_function_argv(const be_jse_symbol_t *s);
bool symbol_is_object(const be_jse_symbol_t *s);
bool symbol_is_array(const be_jse_symbol_t *s);
bool symbol_is_native(const be_jse_symbol_t *s);
bool symbol_is_undefined(const be_jse_symbol_t *s);
bool symbol_is_null(const be_jse_symbol_t *s);
bool symbol_is_basic(const be_jse_symbol_t *s);
bool symbol_is_name(const be_jse_symbol_t *s);
// 新建符号表对象
be_jse_symbol_t *new_symbol(be_jse_symbol_type_e type)
be_jse_symbol_t *new_str_symbol(const char *str)
be_jse_symbol_t *new_int_symbol(be_jse_int_t value)
be_jse_symbol_t *new_bool_symbol(bool value)
be_jse_symbol_t *new_float_symbol(be_jse_float_t value)
be_jse_symbol_t *new_json_symbol(char* json_str, size_t json_str_len)
// 锁定或解锁
be_jse_symbol_t *symbol_lock(be_jse_node_t id);
be_jse_symbol_t *symbol_relock(be_jse_symbol_t *s);
be_jse_node_t    symbol_unlock(be_jse_symbol_t *s);
// 获取符号表的值
be_jse_int_t     get_symbol_int(be_jse_symbol_t *s);
be_jse_bool_t    get_symbol_bool(be_jse_symbol_t *s);
be_jse_symbol_t *get_symbol_value(be_jse_symbol_t *s);
be_jse_bool_t    get_symbol_value_bool(be_jse_symbol_t *s);
be_jse_float_t   get_symbol_double(be_jse_symbol_t *s);
be_jse_int_t     get_symbol_value_int(be_jse_symbol_t *s);
// 获取v的值并解锁v
be_jse_symbol_t *unlock_symbol_value(be_jse_symbol_t *v);
// 字符串相关函数
void symbol_to_str(be_jse_symbol_t *v, char *str, size_t len);
int  symbol_str_len(be_jse_symbol_t *v);
bool symbol_str_equal(be_jse_symbol_t *var, const char *str);
int  symbol_str_cmp(be_jse_symbol_t *va, be_jse_symbol_t *vb, int starta, int startb, bool equalAtEndOfString); 
void symbol_str_append(be_jse_symbol_t *var, const char *str);
void symbol_str_value_append(be_jse_symbol_t *var, be_jse_symbol_t *str, int stridx, int maxLength);
// 运算比较函数
be_jse_symbol_t *symbol_maths_op(be_jse_symbol_t *a, be_jse_symbol_t *b, int op);
be_jse_symbol_t *symbol_value_maths_op(be_jse_symbol_t *a, be_jse_symbol_t *b, int op);
// 拷贝相关函数
be_jse_symbol_t *symbol_cp(be_jse_symbol_t *src);
be_jse_symbol_t *symbol_name_cp(be_jse_symbol_t *src, bool linkChildren, bool keepAsName);
// 子节点相关处理,对象,数组
be_jse_symbol_t *add_symbol_node_name(be_jse_symbol_t* parent, be_jse_symbol_t* child, const char *name);
be_jse_symbol_t *set_symbol_node_name(be_jse_symbol_t *name, be_jse_symbol_t *src);
be_jse_symbol_t *lookup_named_child_symbol(be_jse_node_t parentref, const char *name, bool createIfNotFound);
be_jse_symbol_t *lookup_child_symbol(be_jse_node_t parentref, be_jse_symbol_t *childName, bool addIfNotFound);
void             remove_child_symbol(be_jse_symbol_t *parent, be_jse_symbol_t *child);
// 数组相关函数
be_jse_int_t     get_symbol_array_length(be_jse_symbol_t *v);
be_jse_symbol_t *get_symbol_array_item(be_jse_symbol_t *arr, int index); 
be_jse_symbol_t *get_symbol_array_index(be_jse_symbol_t *arr, be_jse_symbol_t *value); 
be_jse_int_t     symbol_array_push(be_jse_symbol_t *arr, be_jse_symbol_t *value);
be_jse_symbol_t *symbol_array_pop(be_jse_symbol_t *arr);
  • 词法分析 JSE支持的词法类型be_lexer_token_type_e
// 从字符串输入中做词法分析
void be_jse_lexer_init(be_jse_lexer_ctx_t *lex, const char *src, int startPos, int endPos);
// 用于for/while循环中分析条件变量
void be_jse_lexer_init2(be_jse_lexer_ctx_t *lex, be_jse_lexer_ctx_t *initFrom, int startPos);
void be_jse_lexer_deinit(be_jse_lexer_ctx_t *lex);
void be_jse_lexer_reset(be_jse_lexer_ctx_t *lex);
// token相关函数
bool lexer_token_cmp(be_jse_lexer_ctx_t *lex, int expected_token);
char *lexer_get_token(be_jse_lexer_ctx_t *lex);
  • 语法分析
void be_jse_executor_init(be_jse_executor_ctx_t *executor);
void be_jse_executor_deinit(be_jse_executor_ctx_t *executor);
bool be_jse_add_native_func(be_jse_executor_ctx_t *executor, const char *funcDesc, be_jse_callback callbackPtr);
be_jse_symbol_t *be_jse_eval_string(be_jse_executor_ctx_t *executor, const char *str);
bool be_jse_execute_func(be_jse_executor_ctx_t *executor, be_jse_symbol_t *func, int argCount, be_jse_symbol_t **argPtr);
bool be_jse_handle_function(be_jse_parse_skip_flag_e skipName, be_jse_symbol_t **a, be_jse_symbol_t **b, be_jse_symbol_t **c, be_jse_symbol_t **d);
bool be_jse_is_none_arg_function();
be_jse_symbol_t *be_jse_handle_single_arg_function();

如何输出日志?

建议使用AOS系统中的LOG输出系统

定义在.c文件的第一个.h文件之前
#define CONFIG_LOGMACRO_DETAILS

推荐使用
LOGD LOGW LOGE

关于自测

完整性自测要求

  • 测试程序放到apps/js/index.js,模组启动之后会自动执行
  • 测试API完整覆盖,回调或事件通知需要做稳定性测试,避免内存泄露

程序crash的调试方法

kernel panic,err 1200!
assertion "0" failed: file "platform/mcu/esp32/aos/soc_impl.c", line 35, function: soc_err_proc
abort() was called at PC 0x4013826b on core 0

Backtrace: 0x40088af0:0x3fffe4a0 0x40088bef:0x3fffe4c0 0x4013826b:0x3fffe4e0 0x4008b2c1:0x3fffe510 0x400859bd:0x3fffe530 0x40084706:0x3fffe550 0x4008b985:0x3fffe570 0x4008b962:0x3fffe590 0x400862cc:0x3fffe5b0 0x4008675f:0x3fffe5d0 0x40086bc8:0x3fffe5f0 0x400d3bee:0x3fffe610 0x400d3e82:0x3fffe640 0x400d26fb:0x3fffe660 0x40089f4e:0x3fffe6b0 0x401152a9:0x3fffe6e0 0x4011deec:0x3fffe700

1. gdb加载对应的elf文件
./xtensa-esp32-elf-gdb.exe   ./[email protected]

2. 查看Backtrace中地址对应的函数
(gdb) info symbol 0x4008675f
krhino_sem_take + 135 in section .iram0.text
(gdb) info symbol 0x40086bc8
espos_sem_take + 12 in section .iram0.text
(gdb) info symbol 0x400d3bee
xRingbufferReceiveGeneric + 26 in section .flash.text
(gdb) info symbol 0x400d3e82
xRingbufferReceive + 14 in section .flash.text
(gdb) info symbol 0x400d26fb
uart_read_bytes + 175 in section .flash.text
(gdb) info symbol 0x40089f4e
hal_uart_recv_II + 18 in section .iram0.text
(gdb) info symbol 0x401152a9
aos_uart_recv + 17 in section .flash.text
(gdb) info symbol 0x4011deec
task_uart_recv + 100 in section .flash.text

⚠️ **GitHub.com Fallback** ⚠️