Quikjs简介

QuickJS是一个小型的可嵌入Javascript引擎。它支持ES2020规范,包括模块、异步生成器和代理。它还支持数学扩展,比如大整数(BigInt)、大浮点数(BigFloat)和操作符重载。Sifli在Quickjs官方基础上增加了RT-Thread和LVGL的移植适配,沿用原有的C应用框架,使用户可以用javascipt来开发表盘/应用/AOD。

应用框架支持

Sifli 有明确的应用框架,在此基础上添加了应用/表盘/AOD的注册接口,系统启动时,会自动扫描qjs目录,应用子目录扫描qjs_app,表盘扫描子目录qjs_wf,AOD扫描qjs_aod 如果:

  • 发现目录以JA_开头,JA_xxx
    这是一个js的应用,JA_xxx子目录src中的JA_xxx_main.js是应用的主程序,需要js代码中定义一个JA_xxx,基于app,注册到全局变量中。

  • 发现目录是以JW_开头,JW_xxx
    这是一个js的表盘,JW_xxx子目录src中的JW_xxx_main.js是表盘的主程序,需要js代码中定义一个JW_xxx,基于app,注册到全局变量中。

  • 发现目录是以AOD_开头,AOD_xxx
    这是一个js的AOD应用,AOD_xxx子目录src中的AOD_xxx_main.js是AOD的主程序,需要js代码中定义一个AOD_xxx,基于app,注册到全局变量中。
    根据类型,注册不同的C框架中,由C框架统一调度执行。

LVGL的支持

lvgl的支持主要有obj class, obj extension, functions三种

  • obj class obj class封装了lv_obj_xxx函数,输出一个lvobj class,每个method是相应lv_obj_xxx的封装

  • obj extension obj extension封装了lv_class_xxx函数,和生成的class.js配合,输出class,如 label.js和封装的lv_label_xxx,定义了label class,用户使用的时候,只需要import label from “/support_script/qjs/label.js”,就可以使用lable class了。

  • functions functions封装了在lvgl.h和lvsf中,不是以上两种的函数。function属于lvobj模块,使用时需要通过lv模块调用,如gui_app_run(app),js中使用为lv.gui_app_run(app)
    注意
    目前函数的或者返回值是int/bool/color16_t/string/object之外的,无法生成。lv_obj_add_event_cb的参数类型是函数指针,无法生成,所以在lvobj module在C中实现了set_event_cb方法,通过定义一个proxy来实现event事件传递。

demo使用

quickjs支持板级/模拟器运行,使用步骤如下:

  1. 打开HCPU工程/SIMU工程quickjs依赖宏

  2. 勾选预置qjs脚本和应用

  3. 勾选编译选项JS应用,然后开始编译

    注意
    只修改JS应用中的代码,只需单独编译烧录JS应用即可

目录结构

框架代码

QJS框架代码放置在qjs_framework目录中,包含了应用框架,页面调度,lv,lv_ext内置模块等处理。

生成代码

qjs c映射代码放置qjs_generated目录中。

  • lv_qjs_ext_obj.c 是obj extension封装集合

  • lv_qjs_function.c 是functions封装集合

  • lv_qjs_obj.c 是obj class封装集合

  • lv_qjs_generated.c 是文件包含和一些编译打桩函数

class代码

js class是obj externsion的封装,放置在如下图所示目录,使用时需要通过butterfli预置到文件系统中使用。

应用代码

  • qjs_aod存放js aod应用,子目录必须以AOD_打头

  • qjs_app存放js app应用,子目录必须以JA_打头

  • qjs_wf存放js wf应用,子目录必须以JW_打头

  • readme.init 编译依赖,参考 外置应用编译依赖

  • JA_app1_language.js,编译时由多语言excel表转换生成,提供了app_get_str方法获取多语言

  • JA_app1_main.js 主程序,命名必须是JA_xxx_main.js规则

  • JA_app1_page1.js 子页面,page1是子页面名,需要和js 代码中匹配,JA_app1_page2.js 同理,命名规则同主程序

  • resource目录,资源放置目录,参考资源位置

  • 应用图标必须命名为thumb.png

QJS生成

QJS新增的接口建议统一放到lv_qjs_generated.c/h中,对于生成的接口有如下要求:

  • 参数和返回值类型只能是int,bool,lv_color_t,string,lv_obj_t*,接口名以lv_打头

  • 入参个数最大支持8

  • 为了避免生成时编译报错,新增函数尽量不要包含除了LVGL相关的文件,如果需要用到其中的函数,通过export的方式

自动生成

自动生成涉及到以下脚本:

脚本

目录

说明

sol_gen.sh

sdk\external\micropython\lib\lv_bindings

linux系统使用,生成json中间件

qjs_gen_blacklist.bat

solution\components\quickjs\script

windows系统使用,生成black_list.py

qjs_gen_js_class.bat

solution\components\quickjs\script

windows系统使用,生成ext obj对应的.js

qjs_gen_lv_ext_obj.bat

solution\components\quickjs\script

windows系统使用,生成对ext obj的C支持

qjs_gen_lv_functions.bat

solution\components\quickjs\script

windows系统使用,生成对function的C支持

qjs_gen_lv_obj.bat

solution\components\quickjs\script

windows系统使用,生成对lv obj的C支持

inherit_list.py

solution\components\quickjs\script

指明生成JS class继承的模块

string_list.py

solution\components\quickjs\script

指定接口参数为字符串

预处理

  • 继承非lvobj模块的js class,需要在inherit_list.py中指明其继承的父类

  • C控件中有些接口参数可能是void*,但是实际上参数为char* 字符串,如果不修改C接口,可以string_list.py中指明这些接口,脚本会做处理

脚本执行

自动生成按照以下步骤:

  1. 对于lv obj和function类型的接口,将需要新增的接口实现放到lv_qjs_generated.c中,申明放到lv_qjs_generated.h, ext obj类型直接在lv_qjs_generated.h中include

  2. linux系统, 需要安装python2.7版本,(建议通过Ubuntun 18.04.6 LTS版本, 默认安装了python2.7), 进入到工程/sdk/external/micropython/lib/lv_bindings目录

  3. 执行带参的脚本./sol_gen.sh,如 ./sol_gen.sh nand 16 qjs 会生成基于nand工程,16位色的json中间件

  4. windows进入到solution/components/quikcjs/script目录,执行qjs_gen_blacklist.bat生成blacklist.py,所有在black_list中的函数不会生成,可以减少ram的使用,用户根据需求打开或者关闭

  5. 根据添加的函数类型对应执行脚本:

    • 如果是lv obj类型,执行qjs_gen_lv_obj.bat,会更新../qjs_generated/lv_qjs_obj.c文件,在文件中查找是否有新增接口生成

    • 如果是function类型,执行qjs_gen_lv_functions.bat,会更新../qjs_generated/lv_qjs_functions.c文件,在文件中查找是否有新增接口生成

    • 如果是ext obj类型,执行qjs_gen_lv_ext_obj.bat,会更新../qjs_generated/lv_qjs_ext_obj.c文件,在文件中查找是否有新增接口生成,同时执行qjs_gen_js_class.bat,更新../../support_script/qjs目录下的js程序

注意

  • 脚本sol_gen.sh执行可能会报错,可能原因文件不是unix格式,编译C文件有未知字符或者格式不是utf8等,可以根据报错添加打印定位

  • .bat脚本生成的接口受black_list.py中的接口影响,在black_list.py中的接口不会生成

  • 不支持的接口会在生成的.c中提示,可以搜索查看具体原因

手动添加

C文件修改

对quickjs有一定程序了解后,可以通过手动添加来增加接口,好处是不需要处理编译生成中间件的问题,只需要根据函数类型修改qjs_generated对应的.c,如function类型修改lv_qjs_functions.c

类型

文件

枚举定义表

函数格式表

函数列表

回调接口

lvobj

lv_qjs_obj.c

JS_lv_obj_methods_enum

lv_obj_protos

js_lv_obj_methods

lv_obj_call_method

ext obj

lv_qjs_ext_obj.c

JS_lv_ext_funcs_enum

lv_ext_funcs_proto

js_lv_ext_funcs

lv_ext_call_func

functions

lv_qjs_functions.c

JS_lv_funcs_enum

lv_funcs_proto

js_lv_funcs

lv_obj_call_method

  1. 定义枚举值
    在枚举定义表中添加接口对应的enum值, functions和ext obj类型enum名按照LVFUNC+去掉lv打头的函数名,如lv_version_major,enum就是LVFUNC_version_major,lvobj类型以LVOBJ+去掉lv_obj的函数名,如lv_obj_clean,enum就是LVOBJ_clean

  2. 添加数据格式
    在函数格式表中按照在枚举定义表中的顺序添加函数的数据格式。name属性是去掉lv_或者lv_obj_打头的函数名字符串,param_type是数组,根据函数的参数顺序依次填入类型,没有入参忽略, return_type是返回值类型

  3. 添加函数列表 在函数列表中按照在枚举定义表的顺序顺序添加函数定义JS_CFUNC_MAGIC_DEF, 参数1是步骤2中的name,参数2是入参的个数,需要步骤2中param_type的个数匹配,参数3是函数回调接口,根据函数类型填写, 参数4是步骤1中的枚举值

  4. 增加回调实现 对于functions和ext obj类型的对应的回调实现中,C接口的入参根据个数依次使用param[0],param[1]…,对于lv obj类型回调实现中的,第一个参数应该是s->lv_obj,后续的参数依次是param[0],param[1]…,函数的返回值根据类型调用接口返回。

    • 返回值是int,lv_obj_t*类型,调用JS_NewInt32

    • 返回值是bool类型,调用JS_NewBool

    • 返回值是字符串类型,调用JS_NewString

    • 返回值是lv_color_t,先调用lv_color_to32转成uint32_t,再调用JS_NewInt32

参数或者返回值的类型有:

enum {
	LVTYPE_none=0,
	LVTYPE_int,
	LVTYPE_bool,
	LVTYPE_func,    //固定为lv_event_cb_t使用
	LVTYPE_object,
	LVTYPE_color,
	LVTYPE_string,
};

示例:

typedef enum
{
    ...,
    LVFUNC_version_info,
    LVFUNC_version_major,
    LVFUNC_version_minor,
    LVFUNC_version_patch,
}JS_lv_funcs_enum;
const JS_lv_protos lv_funcs_proto[]=
{
    ....,
    //顺序要和枚举中的顺序保持对应
    {
        .name = "version_info",
        .return_type = LVTYPE_string
    },
    {
        .name = "version_major",
        .return_type = LVTYPE_int
    },
    {
        .name = "version_minor",
        .return_type = LVTYPE_int
    },
    {
        .name = "version_patch",
        .return_type = LVTYPE_int
    }, 
};
const JSCFunctionListEntry js_lv_funcs[] =
{
    ...,
    //顺序要和枚举中的顺序保持对应
    JS_CFUNC_MAGIC_DEF("version_info", 0, js_lv_func, LVFUNC_version_info),
    JS_CFUNC_MAGIC_DEF("version_major", 0, js_lv_func, LVFUNC_version_major),
    JS_CFUNC_MAGIC_DEF("version_minor", 0, js_lv_func, LVFUNC_version_minor),
    JS_CFUNC_MAGIC_DEF("version_patch", 0, js_lv_func, LVFUNC_version_patch),    
};
static JSValue  lv_call_func(JSContext *ctx, int magic, JSValueConst param[]) = 
{
    JSValue  r = JS_UNDEFINED;
    switch(magic)
    {
    ...,
    case LVFUNC_version_info:
    {
        char *r_in = (char *)lv_version_info();
        r = JS_NewString(ctx, r_in);
        break;
    }
    case LVFUNC_version_major:
    {
        int r_in = lv_version_major();
        r = JS_NewInt32(ctx, r_in);
        break;
    }
    case LVFUNC_version_minor:
    {
        int r_in = lv_version_minor();
        r = JS_NewInt32(ctx, r_in);
        break;
    }
    case LVFUNC_version_patch:
    {
        int r_in = lv_version_patch();
        r = JS_NewInt32(ctx, r_in);
        break;
    }
    }
    return r;
}

lv obj类型和ext obj类型添加方式类似。

JS文件添加

对于ext obj类型,还需要添加对应的js文件才能使用。在support_scipt/qjs/目录下,添加对应的js文件,如lv_qrcode控件,需要添加lvsfbarcode.js,实现lvsfbarcode这个类 示例:

import * as lv from "lv"; //lv模块
import * as lvext from "lvext"; //lvext模块
import {img} from "/support_script/qjs/img.js"; //img模块
export class lvsfbarcode extends img { //继承自img,需要和C代码控件class变量中的base_class保持一致
	constructor(parent,parentobj=undefined) {
		var nativeobj=parentobj;
		if(nativeobj==undefined) {
			nativeobj=lvext.lvsfbarcode_create(parent);
		}
		super(parent,nativeobj);
		this.nativeobj=nativeobj;
		this.set_obj(this.nativeobj);
	}
	set_text(text){
		return lvext.lvsfbarcode_set_text(this.nativeobj, text);
	}
	set_line_color(index){
		return lvext.lvsfbarcode_set_line_color(this.nativeobj, index);
	}
	set_parse(parse){
		return lvext.lvsfbarcode_set_parse(this.nativeobj, parse);
	}
	set_dir(dir){
		return lvext.lvsfbarcode_set_dir(this.nativeobj, dir);
	}
}
const lv_obj_class_t lv_lvsfbarcode_class =
{
    .constructor_cb = lv_lvsfbarcode_constructor,
    .destructor_cb = lv_lvsfbarcode_destructor,
    .instance_size = sizeof(lv_lvsfbarcode_ext_t),
    .base_class = &lv_img_class //c代码中是继承img
};

注意

  • 为了减少对生成的依赖和ROM的占用,强烈建议对接口进行整理归类,如通过传入不同的enum值,封装get/set字符串,获取整形等的统一接口

支持功能

数据订阅

JS中支持控件可以通过lvobj模块的bind的方法订阅数据, 当数据更新后,C端进行通知,示例如下:

    label = new label(this.root);


    //如果C端通知时没有数据,那么val就不存在
    function callback(id, type, val){
            //do something
    }

    //id 字符串类型,id可为NULL,type必须为唯一值
    //type 整形
    //回调函数
    label.bind(id, type, callback);

注意

  • 建议每个控件只订阅一个类型的数据,就可以不再回调里判断id和type

  • 可以封装get接口,再消息回调中直接调用获取,就无需关心val类型以及是否存在

  • 控件的取消订阅处理由框架管理,用户不需要手动取消

  • 使用示例参考JA_app1

编码器

lvapp模块提供wheel()方法来进行旋钮编码器注册和反注册接口功能,典型使用方式在js app的resume方法中注册,在pause方法中反注册,示例如下

    wheel_handler(key) {
        //do something
    }

    resume() {
        this.wheel( //传入参数时表示注册
            function (key) {
                this.wheel_handler(key);
            }
        );
    } 
    pause(){
        this.wheel(); //没有参数表示反注册
    }

注意

  • key只有17,18两个值,17表示向上,18表示向下,见lv_enums.js

  • 使用示例参考JA_app2

按键

lvapp模块提供keypad()方法来进行按键注册和反注册接口功能,典型使用方式在js app的resume方法中注册,在pause方法中反注册,示例如下

    keypad_handler(key, state) {
        if(key == lv_enums.KEY_HOME && state == 1){
            return 1;   //返回1不执行按键默认功能
        }
        return 0; //返回0执行按键默认功能
    }

    resume() {
        this.keypad( //传入参数时表示注册
            function (key, state) {
                return this.keypad_handler(key, state);
            }
        );
    } 
    pause(){
        this.keypad(); //没有参数表示反注册
    }

注意

  • 按键回调中需要返回值,框架会判断返回值来决定是否执行默认功能

  • 使用示例参考JA_app2

数据存储

qjs框架中的nvm模块通过文件系统操作,将数据序列化为JSON字符串写入文件实现存储功能,读取文件并反序列化为object来实现读取功能,使用示例如下

    import * as nvm from "nvm" //头文件引入

    var user = {
        id:1,
        name:"Bob",
        scores:[90,85]
    };

    var str_data = JSON.stringify(user)
    //filename,  str_data 序列化后的JSON字符串
    nvm.wirte(filename, str_data);

    //读取文件字符串
    var read_string = nvm.read(filename);

    //反序列化为object
    var conv_obj = JSON.parse(read_string);

注意

  • 数据存储/读取时需要考虑异常场景,如文件不存在,数据格式发生变化等

  • 使用示例参考JA_app3

多语言

qjs支持多语言,通过将excel表翻译成js代码,从而实现多语言功能, 按照以下步骤来使用多语言

  1. js程序中导入多语言接口,如 import {app_get_str} from “./JA_app1_language.js”

  2. js代码中通过app_get_str接口获取当前多语言 注意
    在excel转换js过程中,工具会移除未在代码中调用的多语言条目, 由于JS APP/WF/AOD的标题都是在C代码中获取和显示,所以为了避免名称被移除,需要在代码中显示调用以下应用名的多语言获取,如下:

文件操作

quickjs支持文件操作相关接口在 os module。

    /*Open a file. Return a handle or < 0 if error.
    Supported flags:
    O_RDONLY
    O_WRONLY
    O_RDWR
    O_APPEND
    O_CREAT
    O_EXCL
    O_TRUNC
    */
    open(filename, flags, mode = 0o666)

    //Seek in the file. Use std.SEEK_* for whence. offset is either a number or a BigInt. If offset is a BigInt, a BigInt is returned too
    seek(fd, offset, whence)

    //Read length bytes from the file handle fd to the ArrayBuffer buffer at byte position offset. Return the number of read bytes or < 0 if error
    read(fd, buffer, offset, length)
    
    //Write length bytes to the file handle fd from the ArrayBuffer buffer at byte position offset. Return the number of written bytes or < 0 if error.    
    write(fd, buffer, offset, length)

    //Return [str, err] where str is the current working directory and err the error code.
    getcwd()

    //Change the current directory. Return 0 if OK or -errno.
    chdir(path)

    //Create a directory at path. Return 0 if OK or -errno.
    mkdir(path, mode = 0o777)

    //Return [array, err] where array is an array of strings containing the filenames of the directory path. err is the error code. array contains at least "." and ".." if successful.
    readdir(path)

    //Close the file handle fd 
    close(fd)

注意

动画

提供了动画功能的模块,支持多实例,支持接口参考solution\components\quickjs\qjs_framework\lvgl_anim_qjs.c,示例如下

import * as lv from "lv"
import { app } from "lvapp"
import { anim }  from "anim"    //anim 模块

class ja_app extends app {
	constructor() {
		super();
        this.anim = undefined;
    }

    start() {
        print("start");
        this.anim = new anim();
        a.set_values(0,  200); //x or y
        a.set_path(a.PATH_LINEAR)
        a.set_time(1000);
        a.set_delay(2000);
        a.set_start_cb(function(anim){
            print("anim start");
        });
        a.set_ready_cb(function(anim){
            print("anim ready");
        });
        a.set_exec_cb(function(val){
            print(val);
        });
        //a.set_playback_time(200);
        //a.set_playback_delay(200);
        a.set_early_apply(false);
        a.start();
    }

    resume() {
        print("resume");
    }
    pause(){
        print("pause");
    }
    stop(){
        print("stop");
        this.anim.del(); //避免循环引用
    }
}

globalThis.JA_app5 = {
    root:ja_app
};
app_get_str("key_qjs_name","ja_app5");

注意

  • 在callback中如果存在循环引用,退出应用时需要将涉及的控件置空,打破循环引用

  • anim使用demo参考JA_app5

  • 实例化动画模块的数量会影响js页面的帧率

SENSOR

内置封装了全局sensor模块,便于获取sensor数据,支持接口参考solution\components\quickjs\qjs_framework\lvgl_sensor_qjs.c,示例如下

    var level = sensor.BatLevel;

注意

  • sensor模块只支持getter,不支持setter

  • sensor模块是全局,JS代码中无需import

控件

TBD.

应用开发

方法实现

QJS 应用开发是通过创建一个继承自父类app 的class,在这个class中实现一些方法,从而使应用能够得到框架调度实现运行。

父类class app提供了以下方法使用:

  • start() 应用初始化,子类实现

  • resume() 应用恢复,子类实现

  • pause() 应用暂停,子类实现

  • stop() 应用销毁,子类实现

  • root() 子类用来获取当前的parent,创建控件必须基于此接口获取的parent创建

  • path() 子类获取当前的应用的路径,和具体的资源名组合成完整路径

  • keypad() 子类用以注册按键回调,见按键

  • wheel() 子类用以注册编码器回调,见编码器

  • task() 父类通过的task,只支持一个,子类创建或者销毁task,一般在resume()方法中创建,pause()方法中销毁,示例使用如下

pause(){
    this.task(); // 销毁
}

resume(){
    this.task(){
        function(){
            //do something; //处理
        },
        1000    //刷新间隔
    };
}

子类class需要实现4个父类method:

  • start() 页面初始化,主要用来创建页面,获取初始化数据等

  • resume() 页面恢复,一般用来开启task

  • pause() 页面暂停,一般用来暂停task

  • stop() 页面销毁,一般用来释放自定义的控件数据
    注意:

  • 不能存在空方法,如果某个方法为空,要么不实现,要么添加一个print打印

  • 应用框架使用lv obj的user_data,所以不能在js代码中或者自定义的控件使用user_data

  • task 刷新不能过于频繁,不能做大量计算和逻辑,一般都是推荐task中只调用接口,让C处理,提高效率

接口注册

qjs应用(app/wf/aod)通过定义全局唯一object来提供调用处理。以JA_app1应用为例,具体如下:

  1. 应用主程序页面(JA_app1_main.js)中定义object并初始化

    globalThis.JA_app1 = { 
        root : app_root,
    };
  1. 其他子页面中追加定义object的属性

    globalThis.JA_app1.page1 = app_page1;

说明

  • 主程序页面中定义的变量名JA_app1必须和应用的目录名保持一致

  • 主程序页面中的属性名必须固定为root,值为页面代码中定义的class名

  • 追加的属性名page1必须和子页面文件名后半部相同应用名_page1.js,值是页面代码中定义的class名

  • 在js的回调方法中,需要注意循环引用问题

APP开发

主页面文件命名规则是应用名_main.js,此例中为JA_app1_main.js

import * as lv from "lv"    //lv obj module
import * as os from "os"    //os模块
import * as std from "std"  //std模块
import {app} from "lvapp"   //app模块
import * as lv_enums from "/support_script/qjs/lv_enums.js" //枚举模块
import {qrcode} from  "/support_script/qjs/qrcode.js"      //qrcode控件
import {lvsfbarcode} from "/support_script/qjs/lvsfbarcode.js" //barcode 控件
import {app_get_str} from "./JA_app1_language.js"          //引入多语言接口


//arraybuffer转字符串
function arrayBufferToString(buffer){
    return String.fromCharCode.apply(null, new Uint16Array(buffer));
}

//字符串转arraybuffer
function stringToArrayBuffer(str) {
    var buf = new ArrayBuffer(str.length * 2);
    var bufView = new Uint16Array(buf);
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}

var string = "0705632441974";
var txt = "0123456-ABC-abcd";

class JA_app1 extends app{	//继承父类app, class 名需要和目录名相同
    constructor() {
		super();    //构造函数中调用父类app的构造函数
        this.count = 0; //初始化变量
    }

    refresh() {
        this.count++;
        print("count ", this.count);
    }

	start() {

		// Demo for QRcode		
		this.qrcode = new qrcode(this.root()); //this.root() 获取当前parent
		this.qrcode.setparam(200, lv.color_make(0xc0,0xc0,0xc0), lv.color_make(0xFF,0xFF,0xFF));
		this.qrcode.set_text(txt, txt.length); 
		this.qrcode.align(lv_enums.ALIGN_TOP_MID, 0, 0);
		
		this.qrcode.set_event_cb( //set_event_cb是固定控件回调处理
			function(event){
				if (event==lv_enums.EVENT_SHORT_CLICKED){
                    print("qrcode event ", event);
					lv.gui_app_self_exit(); //退出应用
                }
			}
		);
						
		// Demo for Barcode
        this.barcode = new lvsfbarcode(this.root());
		this.barcode.set_size(300, 100);
		this.barcode.set_text(string, 66);
        this.barcode.align(lv_enums.ALIGN_BOTTOM_MID, 0, 0);
        this.barcode.set_event_cb(
			function(event){
				if(event == lv_enums.EVENT_SHORT_CLICKED){
                    print("barcode event ", event);
					lv.qjs_app_page_create("page1"); //创建子页面page1
                }
			}
		);  
		
        print("Started

");
    }    
	resume() {

		// Demo for OS file access
		this.f=os.open("/a.txt", os.O_RDWR | os.O_CREAT | os.O_TRUNC); //打开文件
        print("f=",this.f);
        if(this.f > 0){
            const buffer1 = stringToArrayBuffer('Hello, QuickJS File Operations!');
            os.write(this.f, buffer1, 0, buffer1.byteLength) //写入数据
            print("write:", arrayBufferToString(buffer1));
            os.seek(this.f, std.SEEK_SET, 0);
            const buffer = new ArrayBuffer(buffer1.byteLength);
            os.read(this.f,buffer,0, buffer1.byteLength); //读取数据
            print("read:", arrayBufferToString(buffer));
            os.close(this.f);  //关闭文件
        }
		
		// Demo for OS directory
		os.chdir("/support_script/qjs"); //切换目录
		print(os.getcwd());             //获取当前路径
		var local_files=os.readdir("/"); //读取目录
		print(local_files[0].length);
		for (var i=0;i<local_files[0].length;i++)
		{ 		
			print(local_files[0][i]);
		}

		// Demo for task
		this.task(
			function() {
                this.refresh(); //刷新实现
			}
			, 1000  //刷新间隔
		); 
	}
    pause(){
        this.task(); //销毁task
    }
    stop(){
        print("stop"); //stop没有实际执行内容,增加打印。
    }    
}
//初始化赋值
globalThis.JA_app1 = {
    root : JA_app1,
};
app_get_str("key_qjs_name","key_test"); //应用标题申明

多页支持

多页面文件命名规则是应用名_页面名.js,此例中为JA_app1_page1.js

import * as lv from "lv"
import * as os from "os"
import * as lv_enums from "/support_script/qjs/lv_enums.js"
import {app} from "lvapp"
import {label} from "/support_script/qjs/label.js"

class page1 extends app{
    constructor() {
		super();
    }
	start(){
		this.label = new label(this.root());
        this.label.set_local_font(lv_enums.FONT_SUPER, lv.color_make(0xff,0xff,0xff));
        this.label.set_text("subpage 1");
		this.label.align(lv_enums.ALIGN_CENTER, 0, 0);
		
		this.label1 = new label(this.root());
        this.label1.set_local_font(lv_enums.FONT_TITLE, lv.color_make(0xff,0xff,0xff));
        this.label1.set_text("click to subpage 2");
		this.label1.align(lv_enums.ALIGN_CENTER, 0, 60);
		this.label1.add_flag(lv_enums.FLAG_CLICKABLE);
		this.label1.set_event_cb(
			function(event){
				if(event == lv_enums.EVENT_SHORT_CLICKED)
					lv.qjs_app_page_create("page2");
			}
		);  
		
	}
	resume(){
		print("subpage resume");
	}
	pause(){
		print("subpage pause");
	}
	stop(){
		print("subpage stop");
	}
}

globalThis.JA_app1.page1 = page1;

WF开发

import * as lv from "lv"
import {app} from "lvapp"
import {analogclk} from "/support_script/qjs/analogclk.js"

class JW_wf1 extends app{ //继承父类app, class 名需要和目录名相同
    constructor() {
		super();
    }	
	start() {
        this.anaclk=new analogclk(this.root());
        this.anaclk.pos_off(10,10,105);
        this.anaclk.img(this.path() + "bg.bin", this.path() + "hour.bin", this.path() + "minute.bin", this.path() + "second.bin"); //this.path()获取当前路径
        this.rate = 30;
	}	
    pause() {
        this.anaclk.refr_inteval(0);//暂停模拟表盘
    }    
    resume() {
        this.anaclk.refr_inteval(30);//刷新模拟表盘
    }    
}
globalThis.JW_wf1 = {
    root : JW_wf1;
};

AOD开发

import * as lv from "lv"
import {app} from "lvapp"
import * as lv_enums from "/support_script/qjs/lv_enums.js"
import {analogclk} from "/support_script/qjs/analogclk.js"

class AOD_wf1 extends app{
    constructor() {
		super();
    }	
	start() {
        this.anaclk=new analogclk(this.root());
        this.anaclk.pos_off(10,10,105);
        this.anaclk.img(this.path() + "bg.bin", this.path() + "hour.bin", this.path() + "minute.bin", this.path() + "second.bin");
        this.rate = 30;
	}	
    pause() {
        this.anaclk.refr_inteval(0);
    }    
    resume() {
        this.anaclk.refr_inteval(this.rate);
    }    
}
globalThis.AOD_wf1 = {
    root : AOD_wf1,
};

demo说明

qjs demo应用存放在\solution\examples_dynamic_app\qjs目录中,具体说明如下:

应用

说明

JA_app1

条形码,二维码,文件IO操作,task使用,多页面功能

JA_app2

按键,旋钮功能

JA_app3

数据存储和读取功能

JA_app4

图标功能,可以修改chart_type显示不同风格

JA_app5

多实例动画

JW_wf1

模拟表盘功能

JW_wf2

序列帧功能

JW_wf4

GIF功能

JW_wf5

ezipa功能

JW_wf6

数据订阅功能

AOD_wf1

熄屏显示功能

注意

  • JW_wf5需要硬件支持,不支持模拟器,其余应用模拟器均能正常显示

  • JW_wf6模拟器上无数据

编写建议

  • 枚举类型的js文件建议根据类型进行拆分,不要一个文件过大,提高js解释运行速率

  • 变量名尽量精简,减少内存占用

  • js程序中不建议进行大量计算,可以交给C去执行,js只负责调用接口