完整演示可以观看视频 【富芮坤FR3068x-C】基于JerryScript的智能家居网络显示终端
一、技术点
1. 基于JerryScript的javascript引擎
JerryScript是专为物联网设备设计的轻量级开源JavaScript引擎,目前社区依然活跃。代码可以从 Github获取。
2. LVGL的界面显示
智能家居界面显示,富芮坤的evb_demo已经给了例子,这里就偷懒,直接复用"首页"的显示代码.
3. 基于蓝牙共享上网的HTTP Get实现
网络共享的tcp连接evb_demo中也有实现, 这里只需要在此基础上实现HTTP Get请求.
4. restful API 服务端测试环境
涉及网络访问,用nest.js和vue3搭了一个简单的restful api服务和调试界面.
二、规划思路
1. 总体思路
在evb_demo按键进入首页后,访问测试服务器根API("/")获取javascript脚本,放入 JerryScript中运行. javascript脚本首先显示家居控制界面,之后周期读取sensors API ("/sensors")中的传感器数据更新显示内容.

2. 注册C方法组件
脚本运行需要一些外部组件提供服务,本例将evb_demo中fr_lv_86box_page1_func()函数的显示内容拆分为home、time、date、weather、humidity、air等显示函数,然后在JerryScript中注册作为显示组件供脚本调用。
使用了LWIP的http client的app提供get网络访问,实现Jhttp_getRoot()和Jhttp_get(uint8_t* uri)两个函数,一个访问根API("/"),一个访问sensors API ("/sensors")。其中Jhttp_get(uint8_t* uri)需要在JerryScript中注册外部C方法,以供脚本调用。

3. JerryScript引擎任务
为了让javascript独立运行,在FreeRTOS中为JerryScript创建一个独立任务,通过发送消息来触发javascript脚本的运行。

三、代码实现
1. JerryScript移植
这里是将JerryScript移植如Keil工程。移植很简单,JerryScript支持Amalgamated。 下载源码后执行下面的命令就可以得到
- jerryscript.c
- jerryscript.h
- jerryscript-config.h
- jerryscript-port.c
- jerryscript-math.c
- math.h
python tools/amalgam.py --output-dir amalgam --jerry-core --jerry-port --jerry-math
2. C 方法注册
JerryScript实现了javascript的运行时,如果需要调用板载的其他功能,我们需要将功能C函数注册进JerryScript,参照官网的例子文档也很容易实现。
<pre>static voidregister_js_function (const char *name_p, /**< name of the function */
jerry_external_handler_t handler_p) /**< function callback */
{
jerry_value_t result_val = jerryx_handler_register_global ((const jerry_char_t *) name_p, handler_p);
if (jerry_value_is_error (result_val))
{
printf ("Warning: failed to register '%s' method.", name_p);
}
jerry_release_value (result_val);
} /* register_js_function */
static void jerry_register_contex(void)
{
jerry_value_t func_obj, prop_name;
/* Create an empty JS object */
jerry_value_t object = jerry_create_object ();
func_obj = jerry_create_external_function (jerry_b86_draw_home);
prop_name = jerry_create_string ((const jerry_char_t *)"home");
jerry_release_value (jerry_set_property (object, prop_name, func_obj));
jerry_release_value (prop_name);
jerry_release_value (func_obj);
func_obj = jerry_create_external_function (jerry_b86_draw_time);
prop_name = jerry_create_string ((const jerry_char_t *)"time");
jerry_release_value (jerry_set_property (object, prop_name, func_obj));
jerry_release_value (prop_name);
jerry_release_value (func_obj);
func_obj = jerry_create_external_function (jerry_b86_draw_weather);
prop_name = jerry_create_string ((const jerry_char_t *)"weather");
jerry_release_value (jerry_set_property (object, prop_name, func_obj));
jerry_release_value (prop_name);
jerry_release_value (func_obj);
func_obj = jerry_create_external_function (jerry_b86_draw_date);
prop_name = jerry_create_string ((const jerry_char_t *)"date");
jerry_release_value (jerry_set_property (object, prop_name, func_obj));
jerry_release_value (prop_name);
jerry_release_value (func_obj);
func_obj = jerry_create_external_function (jerry_b86_draw_week);
prop_name = jerry_create_string ((const jerry_char_t *)"week");
jerry_release_value (jerry_set_property (object, prop_name, func_obj));
jerry_release_value (prop_name);
jerry_release_value (func_obj);
func_obj = jerry_create_external_function (jerry_b86_draw_humidity);
prop_name = jerry_create_string ((const jerry_char_t *)"humidity");
jerry_release_value (jerry_set_property (object, prop_name, func_obj));
jerry_release_value (prop_name);
jerry_release_value (func_obj);
func_obj = jerry_create_external_function (jerry_b86_draw_air);
prop_name = jerry_create_string ((const jerry_char_t *)"air");
jerry_release_value (jerry_set_property (object, prop_name, func_obj));
jerry_release_value (prop_name);
jerry_release_value (func_obj);
jerry_value_t global_dobj_val = jerry_get_global_object ();
prop_name = jerry_create_string ((const jerry_char_t *)"draw");
jerry_release_value (jerry_set_property (global_dobj_val, prop_name, object));
jerry_release_value (prop_name);
jerry_release_value (object);
object = jerry_create_object ();
func_obj = jerry_create_external_function (jerry_httpGet);
prop_name = jerry_create_string ((const jerry_char_t *)"get");
jerry_release_value (jerry_set_property (object, prop_name, func_obj));
jerry_release_value (prop_name);
jerry_release_value (func_obj);
prop_name = jerry_create_string ((const jerry_char_t *)"http");
jerry_release_value (jerry_set_property (global_dobj_val, prop_name, object));
jerry_release_value (prop_name);
jerry_release_value (object);
jerry_release_value (global_dobj_val);
}
</pre>
复制代码3. UI和网络 C 方法编写
需要为上面每一个 C 方法编写一个jerry的 C 函数,下面是绘制date和HTTP Get请求的代码示例。其他UI绘制参照jerry_b86_draw_date函数实现即可。
脚本 draw.date()的C实现
<pre>jerry_value_t jerry_b86_draw_date (const jerry_value_t func_obj_val, /**< function object */
const jerry_value_t this_p, /**< this arg */
const jerry_value_t args_p[], /**< function arguments */
const jerry_length_t args_cnt) /**< number of function arguments */
{
(void) func_obj_val; /* unused */
(void) this_p; /* unused */
jerry_value_t ret_val = jerry_create_undefined ();
if(sParent != NULL && args_cnt == 4){
uint8_t month = (uint8_t) jerry_value_as_uint32(args_p[0]);
uint8_t day = (uint8_t) jerry_value_as_uint32(args_p[1]);
uint16_t x = (uint16_t) jerry_value_as_uint32(args_p[2]);
uint16_t y = (uint16_t) jerry_value_as_uint32(args_p[3]);
b86_draw_date(sParent, month, day, x, y);
}
return ret_val;
}</pre>
复制代码脚本 http.get()的C实现
<pre>jerry_value_t jerry_httpGet (const jerry_value_t func_obj_val, /**< function object */
const jerry_value_t this_p, /**< this arg */
const jerry_value_t args_p[], /**< function arguments */
const jerry_length_t args_cnt) /**< number of function arguments */
{
(void) func_obj_val; /* unused */
(void) this_p; /* unused */
if(args_cnt == 1){
jerry_char_t uri[20];
jerry_value_t str_val = jerry_value_to_string (args_p[0]);
jerry_size_t size = jerry_string_to_char_buffer (str_val, uri, 20);
printf("httpGet:%s(%d)", uri, size);
uri[size] = '\0';
Jhttp_get(uri);
jerry_release_value (str_val);
}
if(sHttpDataReady){
jerry_value_t ret_val = jerry_create_string(sDataBuff);
sHttpDataReady = 0;
return ret_val;
}
return jerry_create_undefined ();
}</pre>
复制代码4. javascript任务调度
创建一个FreeRTOS任务,用来做JerryScript的运行任务,UI和网络任务可以通过发送消息来运行javascript脚本。
<pre>void jerry_task_init(void)
{
xTaskCreate(jerry_task, "jerry", 512, NULL, APP_TASK_PRIORITY, &jerry_task_Handle);
}</pre>
复制代码<pre>static void jerry_task(void *arg)
{
jerry_task_msg_t msg;
jerry_queue_handle = xQueueCreate(10, sizeof(jerry_task_msg_t));
printf("jerry_task run.\r\n");
while(1) {
if (xQueueReceive(jerry_queue_handle, &msg, portMAX_DELAY) == pdPASS)
{
printf("jtask msg = %d\r\n", msg.msg_type);
switch(msg.msg_type){
case JERRY_MSG_LAUNCH:
sParent = msg.p_value;
if( pan_get_open_state()){
Jhttp_getRoot();
}else{
b86_draw_noconnect(sParent);
}
break;
case JERRY_MSG_ROOTLOAD:
jerry_setExit(0);
jerry_script_exe(sScriptBuff);
printf("\r\nScript run finish.\r\n");
break;
case JERRY_MSG_EXEC:
jerry_setExit(0);
jerry_script_exe(script);
printf("\r\nScript run finish.\r\n");
break;
}
}
}
}</pre>
复制代码<pre>static void jerry_script_exe(const jerry_char_t* script)
{
jerry_value_t ret_value;
jerry_init (JERRY_INIT_EMPTY);
/* Register functions in the global object. */
register_js_function ("print", jerryx_handler_print);
register_js_function ("sleep", jerry_sleep);
register_js_function ("isExit", jerry_isExit);
jerry_register_contex();
/* Setup Global scope code */
jerry_value_t parsed_code = jerry_parse (NULL, 0, script, strlen ((char*)script), JERRY_PARSE_NO_OPTS);
if (!jerry_value_is_error (parsed_code))
{
/* Execute the parsed source code in the Global scope */
ret_value = jerry_run (parsed_code);
}
int ret_code = JERRY_STANDALONE_EXIT_CODE_OK;
if (jerry_value_is_error (ret_value))
{
printf ("Script Error!\r\n");
ret_code = JERRY_STANDALONE_EXIT_CODE_FAIL;
}
jerry_release_value (ret_value);
jerry_release_value (parsed_code);
jerry_cleanup ();
}</pre>
复制代码5. 服务器测试代码
(略)
四、演示
完整演示可以观看视频 【富芮坤FR3068x-C】基于JerryScript的智能家居网络显示终端
截图一

截图二

五、总结
受益与FR3068x的512KB RAM 和 2MB Flash ,加上 M33的156MHz主频,可以很轻松的运行JerryScript引擎。
因为是测评原因,只是运行了一个简单测试脚本,但也基本演示了从网络访问到UI显示的网络浏览器的实现。FR3068x作为一款高性能的蓝牙MCU,未来可期。
本实验只是简单的测评,实际JerryScript运行时任务需要实现更多功能,外部函数注册也需要详细规划,以发挥javascript的强大动态语言能力。
附件:
实验中运行的脚本代码:
<pre>var layout = {
time:{hour:12, min:16, x:48, y:12},
weather:{temp:16, x:353, y:12},
date:{month:11, day:11, x:59, y:90},
week:{week:0, x:228, y:90 },
humidity:{humi:60, x:50, y:250 },
air:{ico:30, x:290, y:250 }
};
print ('Hello, World!\\r\\n');
draw.home();
draw.time(layout.time.hour, layout.time.min, layout.time.x, layout.time.y);
draw.weather(layout.weather.temp, layout.weather.x, layout.weather.y);
draw.date(layout.date.month, layout.date.day, layout.date.x, layout.date.y);
draw.week(layout.week.week, layout.week.x, layout.week.y);
draw.humidity(layout.humidity.humi, layout.humidity.x, layout.humidity.y);
draw.air(layout.air.ico, layout.air.x, layout.air.y);
while(!isExit()){
var data = http.get("/sensors");
if(data){
data = JSON.parse(data);
if(data.temp){
draw.weather(data.temp, layout.weather.x, layout.weather.y);
}
if(data.humidity){
draw.humidity(data.humidity, layout.humidity.x, layout.humidity.y);
}
if(data.ico){
draw.air(data.ico, layout.air.x, layout.air.y);
}
}
sleep(1000);
};</pre>
复制代码