Browse Source

add: 把airui的加载器代码提交一下,未完成,有时间再写了

Wendal Chen 3 năm trước cách đây
mục cha
commit
5d9fbd7539

+ 147 - 0
components/ui/airui/blocks/luat_airui_block_lvgl.c

@@ -0,0 +1,147 @@
+#include "luat_base.h"
+#include "luat_airui.h"
+#include "luat_malloc.h"
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+typedef struct block_reg
+{
+    const char* comName;
+    airui_block_cb cb;
+}block_reg_t;
+
+int airui_block_lvgl_bar_cb(airui_block_t *bl);
+int airui_block_lvgl_button_cb(airui_block_t *bl);
+int airui_block_lvgl_img_cb(airui_block_t *bl);
+int airui_block_lvgl_label_cb(airui_block_t *bl);
+int airui_block_lvgl_qrcode_cb(airui_block_t *bl);
+int airui_block_lvgl_slider_cb(airui_block_t *bl);
+int airui_block_lvgl_textarea_cb(airui_block_t *bl);
+
+static block_reg_t block_regs[] = {
+    {.comName="LvglButton", .cb=airui_block_lvgl_button_cb},
+    {.comName="LvglImg", .cb=airui_block_lvgl_img_cb},
+    {.comName="LvglLabel", .cb=airui_block_lvgl_label_cb},
+    {.comName="LvglSlider", .cb=airui_block_lvgl_slider_cb},
+    {.comName="LvglBar", .cb=airui_block_lvgl_bar_cb},
+    {.comName="LvglQrcode", .cb=airui_block_lvgl_qrcode_cb},
+    {.comName="LvglLvglTextarea", .cb=airui_block_lvgl_textarea_cb},
+    {.comName = "", .cb = NULL}
+};
+
+
+int top_screen_layout_block(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos, lv_obj_t *scr) {
+    if (tok[pos].type != JSMN_OBJECT) {
+        LLOGD("layout.blocks item must be obj, skip");
+        return 0;
+    }
+    if (tok[pos].size == 0) {
+        LLOGD("layout.blocks item is emtry, skip");
+        return 0;
+    }
+    
+    int name_pos = jsmn_find_by_key(ctx->data, "name", tok, pos);
+    int body_pos = jsmn_find_by_key(ctx->data, "body", tok, pos);
+    int x_pos = jsmn_find_by_key(ctx->data, "x", tok, pos);
+    int y_pos = jsmn_find_by_key(ctx->data, "y", tok, pos);
+    int width_pos = jsmn_find_by_key(ctx->data, "width", tok, pos);
+    int height_pos = jsmn_find_by_key(ctx->data, "height", tok, pos);
+
+    if (name_pos < 1) {
+        LLOGD("layout.blocks item miss name, skip");
+        return 0;
+    }
+    if (body_pos < 1) {
+        LLOGD("layout.blocks item miss body, skip");
+        return 0;
+    }
+    if (x_pos < 1) {
+        LLOGD("layout.blocks item miss x, skip");
+        return 0;
+    }
+    if (y_pos < 1) {
+        LLOGD("layout.blocks item miss y, skip");
+        return 0;
+    }
+    if (width_pos < 1) {
+        LLOGD("layout.blocks item miss width, skip");
+        return 0;
+    }
+    if (height_pos < 1) {
+        LLOGD("layout.blocks item miss height, skip");
+        return 0;
+    }
+
+    air_block_info_t info = {0};
+
+    info.x = jsmn_toint(ctx->data, &tok[x_pos+1]);
+    info.y = jsmn_toint(ctx->data, &tok[y_pos+1]);
+    info.width = jsmn_toint(ctx->data, &tok[width_pos+1]);
+    info.height = jsmn_toint(ctx->data, &tok[height_pos+1]);
+    jsmn_get_string(ctx->data, tok, name_pos+1, &info.name);
+    jsmn_get_string(ctx->data, tok, body_pos+1, &info.body);
+
+    LLOGD("x %d y %d width %d height %d", info.x, info.y, info.width, info.height);
+
+    char tmp[128];
+    mempcpy(tmp, info.body.ptr, info.body.len);
+    tmp[info.body.len] = 0;
+
+    // 查找schema 从而确定 block 的内容, 并创建之
+
+    int schema_top_pos = jsmn_find_by_key(ctx->data, "schema", tok, 0);
+    if (schema_top_pos < 1) {
+        LLOGW("data without schema, skip!");
+        return 0;
+    }
+    int my_schema_pos = jsmn_find_by_key(ctx->data, tmp, tok, schema_top_pos + 1);
+    if (my_schema_pos < 1) {
+        LLOGW("data without my schema [%s], skip!", tmp);
+        return 0;
+    }
+    else {
+        LLOGD("found my schema [%s]", tmp);
+    }
+
+    int comType_pos = jsmn_find_by_key(ctx->data, "comType", tok, my_schema_pos + 1);
+    if (comType_pos < 1) {
+        LLOGW("schema without comType, skip!");
+        return 0;
+    }
+    c_str_t comType = {.len = 0};
+    jsmn_get_string(ctx->data, tok, comType_pos+1, &comType);
+
+    block_reg_t* reg = block_regs;
+    airui_block_t bl = {
+        .ctx = ctx,
+        .info = &info,
+        .parent = scr,
+        .schema_pos = my_schema_pos,
+        .tok = tok,
+        .self = NULL
+    };
+    while (reg->cb != NULL) {
+        if (strlen(reg->comName) == comType.len) {
+            if (!memcmp(reg->comName, comType.ptr, comType.len)) {
+                reg->cb(&bl);
+                if (bl.self != NULL) {
+                    LLOGD("comType %s %p %p", reg->comName, bl.parent, bl.self);
+                    lv_obj_set_pos((lv_obj_t*)bl.self, info.x, info.y);
+                    lv_obj_set_size((lv_obj_t*)bl.self, info.width, info.height);
+                }
+                else {
+                    LLOGD("self is NULL");
+                }
+                return 0;
+            }
+        }
+        reg++;
+    }
+
+    memcpy(tmp, comType.ptr, comType.len);
+    tmp[comType.len] = 0;
+    LLOGW("unsupoort comType [%s]", tmp);
+
+    return 0;
+}

+ 15 - 0
components/ui/airui/blocks/luat_airui_block_lvgl_bar.c

@@ -0,0 +1,15 @@
+#include "luat_base.h"
+#include "luat_airui.h"
+#include "luat_malloc.h"
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+int airui_block_lvgl_bar_cb(airui_block_t *bl) {
+    lv_obj_t *parent = (lv_obj_t*)bl->parent;
+    lv_obj_t *bar = lv_bar_create(parent, NULL);
+    bl->self = bar;
+
+    return 0;
+}
+

+ 46 - 0
components/ui/airui/blocks/luat_airui_block_lvgl_button.c

@@ -0,0 +1,46 @@
+#include "luat_base.h"
+#include "luat_airui.h"
+#include "luat_malloc.h"
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+// #include "lv_btn.h"
+
+int airui_block_lvgl_button_cb(airui_block_t *bl) {
+    //LLOGD("create btn");
+    lv_obj_t *parent = (lv_obj_t*)bl->parent;
+    lv_obj_t *obj = lv_btn_create(parent, NULL);
+    bl->self = obj;
+
+    //---------------------------------------
+    // 取comConf
+    int comConf_pos = jsmn_find_by_key(bl->ctx->data, "comConf", bl->tok, bl->schema_pos + 1);
+    if (comConf_pos < 1) {
+        LLOGD("schema without comConf");
+        return 0;
+    }
+
+    // -----------------------------------
+    // 设置text 属性
+    /*
+    int text_pos = jsmn_find_by_key(bl->ctx->data, "text", bl->tok, comConf_pos + 1);
+    if (text_pos < 1) {
+        LLOGD("label without text");
+        return 0;
+    }
+
+    c_str_t text = {.len = 0};
+    jsmn_get_string(bl->ctx->data, bl->tok, text_pos + 1, &text);
+
+    char* buff = luat_heap_malloc(text.len + 1);
+    // TODO 检查buff是否为NULL
+    memcpy(buff, text.ptr, text.len);
+    buff[text.len] = 0;
+    lv_btn_set_text(obj, buff);
+    luat_heap_free(buff);
+    */
+
+    return 0;
+}
+

+ 38 - 0
components/ui/airui/blocks/luat_airui_block_lvgl_img.c

@@ -0,0 +1,38 @@
+#include "luat_base.h"
+#include "luat_airui.h"
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+#include "luat_malloc.h"
+
+int airui_block_lvgl_img_cb(airui_block_t *bl) {
+    lv_obj_t *parent = (lv_obj_t*)bl->parent;    
+    //LLOGD("create img parent %p", parent);
+    lv_obj_t *obj = lv_img_create(parent, NULL);
+    bl->self = obj;
+
+    // 取src
+    int comConf_pos = jsmn_find_by_key(bl->ctx->data, "comConf", bl->tok, bl->schema_pos + 1);
+    if (comConf_pos < 1) {
+        LLOGD("schema without comConf");
+        return 0;
+    }
+    int src_pos = jsmn_find_by_key(bl->ctx->data, "src", bl->tok, comConf_pos + 1);
+    if (src_pos < 1) {
+        LLOGD("img without src");
+        return 0;
+    }
+    c_str_t src = {.len = 0};
+    jsmn_get_string(bl->ctx->data, bl->tok, src_pos + 1, &src);
+
+    char* buff = luat_heap_malloc(src.len + 1);
+    // TODO 校验buff是否为null
+    memcpy(buff, src.ptr, src.len);
+    buff[src.len] = 0;
+    LLOGD("img src %s", buff);
+    lv_img_set_src(obj, buff);
+    luat_heap_free(buff);
+
+    return 0;
+}
+

+ 42 - 0
components/ui/airui/blocks/luat_airui_block_lvgl_label.c

@@ -0,0 +1,42 @@
+#include "luat_base.h"
+#include "luat_airui.h"
+
+#include "luat_malloc.h"
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+int airui_block_lvgl_label_cb(airui_block_t *bl) {
+    LLOGD("create label");
+    lv_obj_t *parent = (lv_obj_t*)bl->parent;
+    lv_obj_t *obj = lv_label_create(parent, NULL);
+    bl->self = obj;
+
+    //---------------------------------------
+    // 取comConf
+    int comConf_pos = jsmn_find_by_key(bl->ctx->data, "comConf", bl->tok, bl->schema_pos + 1);
+    if (comConf_pos < 1) {
+        LLOGD("schema without comConf");
+        return 0;
+    }
+
+    // -----------------------------------
+    // 设置text 属性
+    int text_pos = jsmn_find_by_key(bl->ctx->data, "text", bl->tok, comConf_pos + 1);
+    if (text_pos < 1) {
+        LLOGD("label without text");
+        return 0;
+    }
+
+    c_str_t text = {.len = 0};
+    jsmn_get_string(bl->ctx->data, bl->tok, text_pos + 1, &text);
+
+    char* buff = luat_heap_malloc(text.len + 1);
+    // TODO 检查buff是否为NULL
+    memcpy(buff, text.ptr, text.len);
+    buff[text.len] = 0;
+    lv_label_set_text(obj, buff);
+    luat_heap_free(buff);
+
+    return 0;
+}

+ 38 - 0
components/ui/airui/blocks/luat_airui_block_lvgl_qrcode.c

@@ -0,0 +1,38 @@
+#include "luat_base.h"
+#include "luat_airui.h"
+#include "luat_malloc.h"
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+lv_obj_t * lv_qrcode_create(lv_obj_t * parent, lv_coord_t size, lv_color_t dark_color, lv_color_t light_color);
+lv_res_t lv_qrcode_update(lv_obj_t * qrcode, const void * data, uint32_t data_len);
+
+int airui_block_lvgl_qrcode_cb(airui_block_t *bl) {
+    lv_obj_t * obj = lv_qrcode_create(bl->parent, 200, lv_color_hex(0xFFFFFF), lv_color_hex(0x000000));
+    bl->self = obj;
+
+    // 首先, 读取comConf
+    int comConf_pos = jsmn_find_by_key(bl->ctx->data, "comConf", bl->tok, bl->schema_pos + 1);
+    if (comConf_pos < 1) {
+        LLOGD("schema without comConf");
+        return 0;
+    }
+
+    // qrcode当前就一个属性, txt, 如果没有就结束
+    int text_pos = jsmn_find_by_key(bl->ctx->data, "text", bl->tok, comConf_pos + 1);
+    if (text_pos < 1) {
+        LLOGD("qrcode without text");
+    }
+    else {
+        // 读取 txt, 设置为默认值
+        c_str_t text = {.len = 0};
+        jsmn_get_string(bl->ctx->data, bl->tok, text_pos + 1, &text);
+
+        if (text.len > 1) {
+            lv_qrcode_update(obj, text.ptr, text.len);
+        }
+    }
+    return 0;
+}
+

+ 17 - 0
components/ui/airui/blocks/luat_airui_block_lvgl_slider.c

@@ -0,0 +1,17 @@
+#include "luat_base.h"
+#include "luat_airui.h"
+#include "luat_malloc.h"
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+int airui_block_lvgl_slider_cb(airui_block_t *bl) {
+    lv_obj_t *parent = (lv_obj_t*)bl->parent;
+    lv_obj_t *obj = lv_slider_create(parent, NULL);
+    bl->self = obj;
+
+
+
+    return 0;
+}
+

+ 41 - 0
components/ui/airui/blocks/luat_airui_block_lvgl_textarea.c

@@ -0,0 +1,41 @@
+#include "luat_base.h"
+#include "luat_airui.h"
+#include "luat_malloc.h"
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+int airui_block_lvgl_textarea_cb(airui_block_t *bl) {
+    lv_obj_t *parent = (lv_obj_t*)bl->parent;
+    lv_obj_t *obj = lv_textarea_create(bl->parent, NULL);
+    bl->self = obj;
+
+    //---------------------------------------
+    // 取comConf
+    int comConf_pos = jsmn_find_by_key(bl->ctx->data, "comConf", bl->tok, bl->schema_pos + 1);
+    if (comConf_pos < 1) {
+        LLOGD("schema without comConf");
+        return 0;
+    }
+
+    // -----------------------------------
+    // 设置text 属性
+    int text_pos = jsmn_find_by_key(bl->ctx->data, "text", bl->tok, comConf_pos + 1);
+    if (text_pos < 1) {
+        LLOGD("label without text");
+        return 0;
+    }
+
+    c_str_t text = {.len = 0};
+    jsmn_get_string(bl->ctx->data, bl->tok, text_pos + 1, &text);
+
+    char* buff = luat_heap_malloc(text.len + 1);
+    // TODO 检查buff是否为NULL
+    memcpy(buff, text.ptr, text.len);
+    buff[text.len] = 0;
+    lv_textarea_set_text(obj, buff);
+    luat_heap_free(buff);
+
+    return 0;
+}
+

+ 471 - 0
components/ui/airui/jsmn.h

@@ -0,0 +1,471 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2010 Serge Zaitsev
+ *
+ * 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.
+ */
+#ifndef JSMN_H
+#define JSMN_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef JSMN_STATIC
+#define JSMN_API static
+#else
+#define JSMN_API extern
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * 	o Object
+ * 	o Array
+ * 	o String
+ * 	o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+  JSMN_UNDEFINED = 0,
+  JSMN_OBJECT = 1 << 0,
+  JSMN_ARRAY = 1 << 1,
+  JSMN_STRING = 1 << 2,
+  JSMN_PRIMITIVE = 1 << 3
+} jsmntype_t;
+
+enum jsmnerr {
+  /* Not enough tokens were provided */
+  JSMN_ERROR_NOMEM = -1,
+  /* Invalid character inside JSON string */
+  JSMN_ERROR_INVAL = -2,
+  /* The string is not a full JSON packet, more bytes expected */
+  JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * type		type (object, array, string etc.)
+ * start	start position in JSON data string
+ * end		end position in JSON data string
+ */
+typedef struct jsmntok {
+  jsmntype_t type;
+  int start;
+  int end;
+  int size;
+#ifdef JSMN_PARENT_LINKS
+  int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string.
+ */
+typedef struct jsmn_parser {
+  unsigned int pos;     /* offset in the JSON string */
+  unsigned int toknext; /* next token to allocate */
+  int toksuper;         /* superior token node, e.g. parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each
+ * describing
+ * a single JSON object.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens);
+
+#ifndef JSMN_HEADER
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
+                                   const size_t num_tokens) {
+  jsmntok_t *tok;
+  if (parser->toknext >= num_tokens) {
+    return NULL;
+  }
+  tok = &tokens[parser->toknext++];
+  tok->start = tok->end = -1;
+  tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+  tok->parent = -1;
+#endif
+  return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
+                            const int start, const int end) {
+  token->type = type;
+  token->start = start;
+  token->end = end;
+  token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+                                const size_t len, jsmntok_t *tokens,
+                                const size_t num_tokens) {
+  jsmntok_t *token;
+  int start;
+
+  start = parser->pos;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+    /* In strict mode primitive must be followed by "," or "}" or "]" */
+    case ':':
+#endif
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+    case ',':
+    case ']':
+    case '}':
+      goto found;
+    default:
+                   /* to quiet a warning from gcc*/
+      break;
+    }
+    if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+      parser->pos = start;
+      return JSMN_ERROR_INVAL;
+    }
+  }
+#ifdef JSMN_STRICT
+  /* In strict mode primitive must be followed by a comma/object/array */
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+#endif
+
+found:
+  if (tokens == NULL) {
+    parser->pos--;
+    return 0;
+  }
+  token = jsmn_alloc_token(parser, tokens, num_tokens);
+  if (token == NULL) {
+    parser->pos = start;
+    return JSMN_ERROR_NOMEM;
+  }
+  jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+  token->parent = parser->toksuper;
+#endif
+  parser->pos--;
+  return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+                             const size_t len, jsmntok_t *tokens,
+                             const size_t num_tokens) {
+  jsmntok_t *token;
+
+  int start = parser->pos;
+  
+  /* Skip starting quote */
+  parser->pos++;
+  
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c = js[parser->pos];
+
+    /* Quote: end of string */
+    if (c == '\"') {
+      if (tokens == NULL) {
+        return 0;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        parser->pos = start;
+        return JSMN_ERROR_NOMEM;
+      }
+      jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+      token->parent = parser->toksuper;
+#endif
+      return 0;
+    }
+
+    /* Backslash: Quoted symbol expected */
+    if (c == '\\' && parser->pos + 1 < len) {
+      int i;
+      parser->pos++;
+      switch (js[parser->pos]) {
+      /* Allowed escaped symbols */
+      case '\"':
+      case '/':
+      case '\\':
+      case 'b':
+      case 'f':
+      case 'r':
+      case 'n':
+      case 't':
+        break;
+      /* Allows escaped symbol \uXXXX */
+      case 'u':
+        parser->pos++;
+        for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
+             i++) {
+          /* If it isn't a hex character we have an error */
+          if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
+                (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
+                (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+            parser->pos = start;
+            return JSMN_ERROR_INVAL;
+          }
+          parser->pos++;
+        }
+        parser->pos--;
+        break;
+      /* Unexpected symbol */
+      default:
+        parser->pos = start;
+        return JSMN_ERROR_INVAL;
+      }
+    }
+  }
+  parser->pos = start;
+  return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
+                        jsmntok_t *tokens, const unsigned int num_tokens) {
+  int r;
+  int i;
+  jsmntok_t *token;
+  int count = parser->toknext;
+
+  for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+    char c;
+    jsmntype_t type;
+
+    c = js[parser->pos];
+    switch (c) {
+    case '{':
+    case '[':
+      count++;
+      if (tokens == NULL) {
+        break;
+      }
+      token = jsmn_alloc_token(parser, tokens, num_tokens);
+      if (token == NULL) {
+        return JSMN_ERROR_NOMEM;
+      }
+      if (parser->toksuper != -1) {
+        jsmntok_t *t = &tokens[parser->toksuper];
+#ifdef JSMN_STRICT
+        /* In strict mode an object or array can't become a key */
+        if (t->type == JSMN_OBJECT) {
+          return JSMN_ERROR_INVAL;
+        }
+#endif
+        t->size++;
+#ifdef JSMN_PARENT_LINKS
+        token->parent = parser->toksuper;
+#endif
+      }
+      token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+      token->start = parser->pos;
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case '}':
+    case ']':
+      if (tokens == NULL) {
+        break;
+      }
+      type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+      if (parser->toknext < 1) {
+        return JSMN_ERROR_INVAL;
+      }
+      token = &tokens[parser->toknext - 1];
+      for (;;) {
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          token->end = parser->pos + 1;
+          parser->toksuper = token->parent;
+          break;
+        }
+        if (token->parent == -1) {
+          if (token->type != type || parser->toksuper == -1) {
+            return JSMN_ERROR_INVAL;
+          }
+          break;
+        }
+        token = &tokens[token->parent];
+      }
+#else
+      for (i = parser->toknext - 1; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          if (token->type != type) {
+            return JSMN_ERROR_INVAL;
+          }
+          parser->toksuper = -1;
+          token->end = parser->pos + 1;
+          break;
+        }
+      }
+      /* Error if unmatched closing bracket */
+      if (i == -1) {
+        return JSMN_ERROR_INVAL;
+      }
+      for (; i >= 0; i--) {
+        token = &tokens[i];
+        if (token->start != -1 && token->end == -1) {
+          parser->toksuper = i;
+          break;
+        }
+      }
+#endif
+      break;
+    case '\"':
+      r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+    case '\t':
+    case '\r':
+    case '\n':
+    case ' ':
+      break;
+    case ':':
+      parser->toksuper = parser->toknext - 1;
+      break;
+    case ',':
+      if (tokens != NULL && parser->toksuper != -1 &&
+          tokens[parser->toksuper].type != JSMN_ARRAY &&
+          tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+        parser->toksuper = tokens[parser->toksuper].parent;
+#else
+        for (i = parser->toknext - 1; i >= 0; i--) {
+          if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+            if (tokens[i].start != -1 && tokens[i].end == -1) {
+              parser->toksuper = i;
+              break;
+            }
+          }
+        }
+#endif
+      }
+      break;
+#ifdef JSMN_STRICT
+    /* In strict mode primitives are: numbers and booleans */
+    case '-':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case 't':
+    case 'f':
+    case 'n':
+      /* And they must not be keys of the object */
+      if (tokens != NULL && parser->toksuper != -1) {
+        const jsmntok_t *t = &tokens[parser->toksuper];
+        if (t->type == JSMN_OBJECT ||
+            (t->type == JSMN_STRING && t->size != 0)) {
+          return JSMN_ERROR_INVAL;
+        }
+      }
+#else
+    /* In non-strict mode every unquoted value is a primitive */
+    default:
+#endif
+      r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+      if (r < 0) {
+        return r;
+      }
+      count++;
+      if (parser->toksuper != -1 && tokens != NULL) {
+        tokens[parser->toksuper].size++;
+      }
+      break;
+
+#ifdef JSMN_STRICT
+    /* Unexpected char in strict mode */
+    default:
+      return JSMN_ERROR_INVAL;
+#endif
+    }
+  }
+
+  if (tokens != NULL) {
+    for (i = parser->toknext - 1; i >= 0; i--) {
+      /* Unmatched opened object or array */
+      if (tokens[i].start != -1 && tokens[i].end == -1) {
+        return JSMN_ERROR_PART;
+      }
+    }
+  }
+
+  return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+JSMN_API void jsmn_init(jsmn_parser *parser) {
+  parser->pos = 0;
+  parser->toknext = 0;
+  parser->toksuper = -1;
+}
+
+#endif /* JSMN_HEADER */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSMN_H */

+ 103 - 0
components/ui/airui/jsmn_help.c

@@ -0,0 +1,103 @@
+#include "luat_base.h"
+
+#include "luat_airui.h"
+#include <stdlib.h>
+
+int jsmn_skip_object(jsmntok_t *tok, size_t *cur) {
+    size_t objlen = tok[*cur].size;
+
+    *cur = *cur + 1;
+
+    for (size_t i = 0; i < objlen; i++)
+    {
+        // 首先跳过key
+        *cur = *cur + 1;
+        jsmn_skip_entry(tok, cur);
+    }
+
+    return 0;
+}
+
+int jsmn_skip_array(jsmntok_t *tok, size_t *cur) {
+    size_t objlen = tok[*cur].size;
+
+    *cur = *cur + 1;
+    
+    for (size_t i = 0; i < objlen; i++)
+    {
+        jsmn_skip_entry(tok, cur);
+    }
+    return 0;
+}
+
+int jsmn_skip_entry(jsmntok_t *tok, size_t *cur) {
+    if (tok[*cur].type == JSMN_STRING || tok[*cur].type == JSMN_PRIMITIVE) {
+        *cur = *cur + 1;
+        return 0;
+    }
+    if (tok[*cur].type == JSMN_OBJECT) {
+        jsmn_skip_object(tok, cur);
+    }
+    if (tok[*cur].type == JSMN_ARRAY) {
+        jsmn_skip_array(tok, cur);
+    }
+    return 0;
+}
+
+int jsmn_find_by_key(const char* data, const char* key, jsmntok_t *tok, size_t pos) {
+    if (tok[pos].type != JSMN_OBJECT) {
+        return -1;
+    }
+    size_t objlen = tok[pos].size;
+    if (objlen == 0) {
+        return -1;
+    }
+    pos ++;
+    size_t keylen = strlen(key);
+    for (size_t i = 0; i < objlen; i++)
+    {
+        if (tok[pos].end - tok[pos].start == keylen) {
+            if (!memcmp(&data[tok[pos].start], key, keylen)) {
+                return pos;
+            }
+        }
+        pos ++;
+        jsmn_skip_entry(tok, &pos);
+    }
+    return -1;
+}
+
+int jsmn_toint(const char* data, jsmntok_t *tok) {
+    if (tok->type != JSMN_PRIMITIVE) {
+        return 0;
+    }
+    int start = tok->start;
+    int end = tok->end;
+    if (end - start > 15) {
+        return 0;
+    }
+    char buff[16] = {0};
+    memcpy(buff, &data[start], end - start);
+    return atoi(buff);
+}
+
+void jsmn_get_string(const char* data, jsmntok_t *tok, int pos, c_str_t *str) {
+    str->len = tok[pos].end - tok[pos].start;
+    str->ptr = &data[tok[pos].start];
+}
+
+
+void jsmn_kv_get(const char* data, jsmntok_t *tok, int pos, const char* key, c_str_t *str) {
+    int pos2 = jsmn_find_by_key(data, key, tok, pos);
+    if (pos2 < 1) {
+        str->len = 0;
+        return;
+    }
+    if (tok[pos2].type == JSMN_ARRAY || tok[pos2].type == JSMN_ARRAY) {
+        str->len = 0;
+        return;
+    }
+
+    str->len = tok[pos2].end - tok[pos2].start;
+    str->ptr = &data[tok[pos2].start];
+}

+ 81 - 0
components/ui/airui/luat_airui.h

@@ -0,0 +1,81 @@
+
+#ifndef LUAT_AIRUI_H
+#define LUAT_AIRUI_H
+
+#include "luat_base.h"
+#include "lvgl.h"
+
+#define JSMN_STATIC
+#include "jsmn.h"
+
+typedef struct luat_airui_obj
+{
+    lv_obj_t *lvobj;
+}luat_airui_obj_t;
+
+
+typedef struct luat_airui_ctx
+{
+    const char* screen_name;
+    int airui_backend_type;
+    lv_obj_t *scr;
+    lv_obj_t *objs;
+    size_t obj_count;
+    const char* data;
+}luat_airui_ctx_t;
+
+typedef int (*airui_parse_cb)(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos);
+
+typedef struct airui_parser
+{
+    const char* name;
+    airui_parse_cb cb;
+}airui_parser_t;
+
+
+int luat_airui_load_buff(luat_airui_ctx_t** ctx, int backend, const char* screen_name, const char* buff, size_t len);
+int luat_airui_load_file(luat_airui_ctx_t** ctx, int backend, const char* screen_name, const char* path);
+int luat_airui_get(luat_airui_ctx_t* ctx, const char* key);
+
+int luat_airui_load_components(luat_airui_ctx_t* ctx, void *tok, size_t tok_count);
+
+// jsmn 的帮助函数
+
+typedef struct c_str
+{
+    size_t len;
+    char* ptr;
+}c_str_t;
+
+typedef struct air_block_info
+{
+    int x;
+    int y;
+    int width;
+    int height;
+    c_str_t name;
+    c_str_t body;
+}air_block_info_t;
+
+
+int jsmn_skip_object(jsmntok_t *tok, size_t *cur);
+int jsmn_skip_array(jsmntok_t *tok, size_t *cur);
+int jsmn_skip_entry(jsmntok_t *tok, size_t *cur);
+int jsmn_find_by_key(const char* data, const char* key, jsmntok_t *tok, size_t pos);
+int jsmn_toint(const char* data, jsmntok_t *tok);
+void jsmn_get_string(const char* data, jsmntok_t *tok, int pos, c_str_t *str);
+
+typedef struct airui_block
+{
+    luat_airui_ctx_t* ctx;
+    jsmntok_t *tok;
+    air_block_info_t* info;
+    int schema_pos;
+    void* parent;
+    void* self;
+}airui_block_t;
+
+
+typedef int (*airui_block_cb)(airui_block_t *bl);
+
+#endif

+ 51 - 0
components/ui/airui/luat_airui_components.c

@@ -0,0 +1,51 @@
+#include "luat_base.h"
+#include "luat_airui.h"
+#include "luat_malloc.h"
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+
+extern airui_parser_t airui_top_parsers[];
+
+int luat_airui_load_components(luat_airui_ctx_t* ctx, void *args, size_t tok_count) {
+    jsmntok_t *tok = (jsmntok_t *)args;
+
+    if (tok->type != JSMN_OBJECT) {
+        LLOGE("json must be a map!!");
+        return -4;
+    }
+
+    jsmntok_t *top = tok;
+    size_t cur = 0;
+
+    // 遍历数据,测试用
+#if 0
+    for (size_t i = 0; i < tok_count; i++)
+    {
+        LLOGD("tok\t%d\t%d\t%d\t%d\t%d", i, tok[i].type, tok[i].start, tok[i].end, tok[i].size);
+    }
+#endif
+
+
+    LLOGD("top size %d", top->size);
+    if (top->size < 3) {
+        LLOGE("not a good ui data. top size < 3");
+        return -5;
+    }
+
+    airui_parser_t* parser = airui_top_parsers;
+    while (parser->cb != NULL)
+    {
+        int pos = jsmn_find_by_key(ctx->data, parser->name, tok, cur);
+        if (pos > 0) {
+            parser->cb(ctx, tok, pos);
+        }
+        else {
+            LLOGD("parser key not found %s", parser->name);
+        }
+        parser ++;
+    }
+
+    return 0;
+}

+ 112 - 0
components/ui/airui/luat_airui_lvgl_screen.c

@@ -0,0 +1,112 @@
+#include "luat_base.h"
+// #include "luat_lvgl.h"
+#include "luat_airui.h"
+#include "luat_malloc.h"
+
+#include "math.h"
+#include <stdlib.h>
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+int top_screen_layout(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos);
+int top_screen_layout_blocks(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos);
+int top_screen_layout_block(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos, lv_obj_t *scr);
+
+int top_screens_one(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos) {
+    LLOGD("parse screen");
+    if (tok[pos].type != JSMN_OBJECT) {
+        LLOGD("screen must be object");
+        return -1;
+    }
+    int name_pos = jsmn_find_by_key(ctx->data, "name", tok, pos);
+    if (name_pos < 1) {
+        LLOGD("screen without name, skip");
+        return 0;
+    }
+    c_str_t name = {.len = 0};
+    jsmn_get_string(ctx->data, tok, name_pos + 1, &name);
+    if (name.len < 1) {
+        LLOGD("screen name is emtry, skip");
+        return 0;
+    }
+
+    if (ctx->screen_name != NULL) {
+        if (name.len != strlen(ctx->screen_name)) {
+            LLOGD("skip screen, name not match");
+            return 0;
+        }
+        if (memcmp(ctx->screen_name, name.ptr, name.len)) {
+            LLOGD("skip screen, name not match");
+            return 0;
+        }
+    }
+
+    //LLOGD("load screen name %.*s", name.len, name);
+
+    int layout_pos = jsmn_find_by_key(ctx->data, "layout", tok, pos);
+    if (layout_pos < 1) {
+        LLOGD("screen without layout, skip");
+        return 0;
+    }
+
+
+    top_screen_layout(ctx, tok, layout_pos);
+
+    return 0;
+}
+
+int top_screen_layout(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos) {
+    LLOGD("screen layout");
+
+    pos ++; // 跳到值
+    if (tok[pos].type != JSMN_OBJECT) {
+        LLOGD("layout must be map, skip");
+        return 0;
+    }
+
+    int blocks_pos = jsmn_find_by_key(ctx->data, "blocks", tok, pos);
+    if (blocks_pos < 1) {
+        LLOGD("layout without blocks, skip");
+        return 0;
+    }
+    
+    top_screen_layout_blocks(ctx, tok, blocks_pos);
+
+    return 0;
+}
+
+int top_screen_layout_blocks(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos) {
+    LLOGD("screen.layer.blocks");
+    
+    // 移动到值
+    pos ++;
+    
+    if (tok[pos].type != JSMN_ARRAY) {
+        LLOGD("layout.blocks must be array, skip");
+        return 0;
+    }
+
+    if (tok[pos].size < 1) {
+        LLOGD("layout.blocks is emtry, skip");
+        return 0;
+    }
+    int block_count = tok[pos].size;
+    LLOGD("layout.blocks count %d", block_count);
+
+    // 移动到blocks[0]
+    pos ++;
+
+    lv_obj_t *scr = lv_obj_create(NULL, NULL);
+
+    for (size_t i = 0; i < block_count; i++)
+    {
+        top_screen_layout_block(ctx, tok, pos, scr);
+        jsmn_skip_object(tok, &pos);
+    }
+    
+    ctx->scr = scr;
+
+    return 0;
+}
+

+ 81 - 0
components/ui/airui/luat_airui_lvgl_top.c

@@ -0,0 +1,81 @@
+#include "luat_base.h"
+// #include "luat_lvgl.h"
+#include "luat_airui.h"
+#include "math.h"
+#include <stdlib.h>
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+static int top_device_parser(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos);
+static int top_schema_parser(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos);
+static int top_screens_parser(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos);
+
+airui_parser_t airui_top_parsers[] = {
+    // 注意顺序, 先处理schema, 再处理screens, 因为后者依赖前者.
+    {.name = "device", .cb = top_device_parser},
+    {.name = "screens", .cb = top_screens_parser},
+    {.name = "schema", .cb = top_schema_parser},
+    {.name = "", .cb = NULL}
+};
+
+// 处理设备信息, 实际上没啥用, 仅调试日志
+static int top_device_parser(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos) {
+    // LLOGD("parse device");
+    // int width_pos = jsmn_find_by_key(ctx->data, "width", tok, pos + 1);
+    // int height_pos = jsmn_find_by_key(ctx->data, "height", tok, pos + 1);
+
+    // if (width_pos > 0 && height_pos > 0) {
+    //     LLOGD("device width %d height %d", 
+    //                 jsmn_toint(ctx->data, &tok[width_pos + 1]),
+    //                 jsmn_toint(ctx->data, &tok[height_pos + 1])
+    //                 );
+    // }
+    // else {
+    //     LLOGD("device width height not found");
+    // }
+
+    return 0;
+}
+
+
+int top_screens_one(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos);
+// 重头戏, 解析screens
+static int top_screens_parser(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos) {
+    LLOGD("parse screens");
+
+    pos ++;
+
+    jsmntok_t* screens = &tok[pos];
+    if (screens->type != JSMN_ARRAY) {
+        LLOGW("screens must be array");
+        return -1;
+    }
+    
+    if (screens->size == 0) {
+        LLOGW("screens is emtry");
+        return 0;
+    }
+    else {
+        LLOGD("screens count %d", screens->size);
+    }
+
+    size_t scount = screens->size;
+
+    pos ++; // 移动到一个元素
+
+    for (size_t i = 0; i < scount; i++)
+    {
+        top_screens_one(ctx, tok, pos);
+        jsmn_skip_object(tok, &pos);
+    }
+    
+
+    return 0;
+}
+
+// 解析schema
+static int top_schema_parser(luat_airui_ctx_t* ctx, jsmntok_t *tok, int pos) {
+    //LLOGD("parse schema");
+    return 0;
+}

+ 68 - 0
components/ui/airui/luat_airui_main.c

@@ -0,0 +1,68 @@
+#include "luat_base.h"
+#include "luat_airui.h"
+#include "luat_malloc.h"
+
+#define LUAT_LOG_TAG "airui"
+#include "luat_log.h"
+
+static jsmn_parser parser;
+
+int luat_airui_load_buff(luat_airui_ctx_t** _ctx, int backend, const char* screen_name, const char* buff, size_t len) {
+    int ret;
+    luat_airui_ctx_t *ctx = luat_heap_malloc(sizeof(luat_airui_ctx_t));
+    if (ctx == NULL) {
+        LLOGW("out of memory when malloc luat_airui_ctx_t");
+        return -1;
+    }
+    
+    // 首先, 初始化处理器
+    jsmn_init(&parser);
+    // 然后,先扫描一遍, 获取总的token数量, 若处理失败,会返回负数
+    ret = jsmn_parse(&parser, buff, len, NULL, 0);
+    if (ret <= 0) {
+        LLOGW("invaild json ret %d", ret);
+        return -2;
+    }
+    LLOGD("found json token count %d", ret);
+    // 再然后, 分配内存
+    jsmntok_t *tok = luat_heap_malloc(sizeof(jsmntok_t) * ret);
+    if (tok == NULL) {
+        luat_heap_free(ctx);
+        LLOGW("out of memory when malloc jsmntok_t");
+        return -3;
+    }
+    // 真正的解析, 肯定不会出错
+    jsmn_init(&parser);
+    ret = jsmn_parse(&parser, buff, len, tok, ret);
+    if (ret <= 0) {
+        // 还是防御一下吧
+        luat_heap_free(tok);
+        luat_heap_free(ctx);
+        LLOGW("invaild json ret %d", ret);
+        return -2;
+    }
+
+    // 现在解析完成了, 开始生成的组件树
+    LLOGD("json parse complete, begin components jobs ...");
+    ctx->data = buff;
+    ctx->screen_name = screen_name;
+    ret = luat_airui_load_components(ctx, tok, ret);
+    LLOGD("json parse complete, end components jobs, ret %d", ret);
+    luat_heap_free(tok);
+    if (ret == 0) {
+        *_ctx = ctx;
+    }
+    else {
+        luat_heap_free(ctx);
+    }
+    return ret;
+}
+
+int luat_airui_load_file(luat_airui_ctx_t** ctx, int backend, const char* screen_name, const char* path) {
+    return -1;
+}
+
+int luat_airui_get(luat_airui_ctx_t* ctx, const char* key) {
+    return -1;
+}
+

+ 51 - 0
components/ui/airui/luat_lib_airui.c

@@ -0,0 +1,51 @@
+#include "luat_base.h"
+
+#include "luat_malloc.h"
+#include "luat_airui.h"
+
+static int l_airui_load_buff(lua_State *L) {
+    size_t len;
+    const char* backend = luaL_checkstring(L, 1);
+    const char* screen_name = luaL_checkstring(L, 2);
+    const char* buff = luaL_checklstring(L, 3, &len);
+
+    luat_airui_ctx_t *ctx = NULL;
+    int ret = luat_airui_load_buff(&ctx, 0, screen_name, buff, len);
+    if (ret == 0) {
+        lua_pushlightuserdata(L, ctx);
+        return 1;
+    }
+    else {
+        lua_pushnil(L);
+        lua_pushinteger(L, ret);
+        return 2;
+    }
+    return 0;
+}
+
+static int l_airui_load_file(lua_State *L) {
+    return 0;
+}
+
+
+static int l_airui_get_scr(lua_State *L) {
+    luat_airui_ctx_t *ctx = lua_touserdata(L, 1);
+    if (ctx == NULL)
+        return 0;
+    lua_pushlightuserdata(L, ctx->scr);
+    return 1;
+}
+
+#include "rotable2.h"
+static const rotable_Reg_t reg_airui[] =
+{
+    { "load_buff" ,             ROREG_FUNC(l_airui_load_buff)},
+    { "load_file" ,             ROREG_FUNC(l_airui_load_file)},
+    { "get_scr" ,               ROREG_FUNC(l_airui_get_scr)},
+    { NULL,                     ROREG_INT(0)}
+};
+
+LUAMOD_API int luaopen_airui( lua_State *L ) {
+    luat_newlib2(L, reg_airui);
+    return 1;
+}