本帖最后由 用户1730898853311 于 2025-1-23 13:24 编辑



完整演示可以观看视频 【富芮坤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")中的传感器数据更新显示内容.
image.png

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方法,以供脚本调用。
image.png

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

三、代码实现

1. JerryScript移植
这里是将JerryScript移植如Keil工程。移植很简单,JerryScript支持Amalgamated。 下载源码后执行下面的命令就可以得到
  • jerryscript.c
  • jerryscript.h
  • jerryscript-config.h
  • jerryscript-port.c
  • jerryscript-math.c
  • math.h
这样很容易的在Keil中添加和编译。需要修改jerryscript-config.h中相关参数,优化code和ram使用。
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的智能家居网络显示终端

    截图一
    d4bd84525b252caccaf926ae3b02ebd.png

    截图二
    5abd03765b6da93c74f8fad33873df9.png


    五、总结

    受益与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>
  • 复制代码