跳转至

通用 - 定义 Block

核心结构

在扩展文件目录的 index.js 文件中通过 getInfo() 方法返回配置对象,必须包含以下结构:

getInfo() {
  return {
    name: '扩展名称',
    menuIconURI: '扩展菜单图标',
    blockIconURI: '扩展积木块图标',
    color1: '#颜色值',
    color2: '#颜色值',
    color3: '#颜色值',
    blocks: [ /* 积木定义列表 */ ],
    menus: { /* 下拉菜单配置 */ }
  };
}

积木定义(blocks 数组)

每个积木对象需配置以下属性:

1. opcode

  • 作用:积木唯一标识符,与代码生成逻辑关联
  • 格式:字符串
  • 示例opcode: 'arduino_set_pinMode'

2. blockType

  • 类型BlockType 枚举值
  • 可选值
类型 形状 用途
HAT 帽子形 事件触发器
COMMAND 方形 执行命令(无返回值)
REPORTER 圆形 返回数据(如传感器读数)
BOOLEAN 菱形 返回布尔值

3. text

  • 作用:定义积木显示文本
  • 多语言支持:建议使用 formatMessage
  • 语法
    text: formatMessage({
      id: '翻译ID',
      default: '默认文本 [参数1] [参数2]'
    }),
    
  • 参数占位符[ARG] 对应 arguments 中的字段

4. group

  • 作用:积木所属功能分组
  • 多语言示例
    group: formatMessage({id: '翻译ID', default: '引脚操作' })
    

5. arguments

  • 作用:定义输入字段(如数字输入框、下拉菜单)
  • 结构
    arguments: {
      参数名: {
        type: ArgumentType.STRING, // field 类型
        defaultValue: 默认值,
        menu: '关联的菜单名',
        inputParams: { /* 特殊输入参数 */ }
      }
    }
    
  • 常用 field 类型
常用类型 说明
STRING 字符串输入
NUMBER 数字输入
SLIDER 滑动条(需配置 rangeMin/rangeMax)

💡 field 用法详细介绍:通用-Field类型总览

1. 简单菜单

menus: {
  pinMenu: ['A0', 'A1', 'A2'] // 自动转 {text:值, value:值}
}

2. 复杂菜单

menus: {
  dynamicMenu: {
    acceptReporters: true, // 是否允许拖入积木
    items: [
      { text: '自定义文本', value: 'CUSTOM' }
    ]
  }
}

3. 动态菜单

可通过类方法动态生成:

_initPinMenu() {
  return [ {
              text: {id: '翻译ID', default: '数字引脚2' },
              value: 'D2',
           } ];
}
menus: {
  pinMenu: this._initPinMenu()
}

注意事项

  • 参数占位符格式必须为 [ARG],且与 arguments 中的字段名一致
  • 滑动条类型必须配置 inputParams.rangeMin/Max
  • 多语言 ID 需与翻译文件中的键名严格对应

模板示例

1. 基础下拉框

{
  opcode: 'select_serial_port',
  text: '选择串口 [PORT]',
  blockType: BlockType.COMMAND,
  arguments: {
    PORT: {
      type: ArgumentType.STRING,
      menu: 'serialPortMenu',  // 对应 menus.serialPortMenu
      defaultValue: 'UART1'
    }
  }
}

2. 带校验的输入框

{
    opcode: 'set_wifi_password',
    text: '设置密码: [PWD]',
    blockType: BlockType.COMMAND,
    arguments: {
        PWD: {
            type: ArgumentType.STRING,
            inputParams: {
                validate: '_validatePassword' // 校验函数
            },
            defaultValue: '12345678'
        }
    }
}

3. 数值范围滑块

{
    opcode: 'set_motor_speed',
    text: '电机速度 [SPEED]%',
    blockType: BlockType.COMMAND,
    arguments: {
        SPEED: {
            type: ArgumentType.NUMBER,
            inputParams: {
                rangeMin: 0,
                rangeMax: 100
            },
            defaultValue: 50
        }
    }
}

特殊用法

1. 下拉框联动机制

定义:根据下拉框 A 的不同选择,下拉框 B 随之展示不同内容。

案例分析: * 场景:当硬串口1/硬串口2切换时,刷新 Rx 的引脚默认值显示和下拉菜单。 * 实现思路: 1. 监听串口切换:记录当前 block 的 id 及串口数据。 2. 监听 Rx 下拉框点击:判断当前的硬串口值,返回不同的下拉菜单列表。

关键点:需要理解 BlockID 的管理机制。Workspace 中每个 block 有唯一的 id。

代码实现

  1. 定义积木 (blocks):

    blocks: [
        {
            opcode: 'pico_serialRemapping',
            text: '选择 [SERIALPORT] Rx [PINR]',
            blockType: BlockType.COMMAND,
            arguments: {
                SERIALPORT: {
                    type: ArgumentType.STRING,
                    menu: 'serialTypeMenu',
                    defaultValue: this.getCurrentSerialDefault(true),
                },
                PINR: {
                    type: ArgumentType.STRING,
                    menu: 'pinRMenu',
                    defaultValue: 'GP1',
                }
            }
        }
    ],
    menus: {
        serialTypeMenu: this._getSerialTypeMenu(),
        pinRMenu: this._getPINRMenu(),
    }
    

  2. 串口切换监听 (Func):

    // block串口数据对象
    this.serialType = {};
    
    // 重置Flyout区block状态
    setDefaultSerial(defaultSerial) {
        let regex = /.*_serialRemapping$/; // block的opcode
        Object.keys(this.serialType).map(_item => {
            if (regex.test(_item)) {
                this.serialType[_item] = defaultSerial;
            }
        })
    }
    
    // 获取/重置串口的默认value
    getCurrentSerialDefault(isReset) {
        if (isReset) this.setDefaultSerial('Hardware Serial1');
        return 'Hardware Serial1';
    }
    
    // 监听串口的切换
    _getSerialTypeMenu() {
        const self = this;
        return {
            items: function () {
                // block创建时, this.sourceBlock_ = null
                if (this.sourceBlock_) {
                    // 设置初始串口值
                    if (!self.serialType.hasOwnProperty(this.sourceBlock_.id)) {
                        let defaultSerial = self.getCurrentSerialDefault(false);
                        self.serialType[this.sourceBlock_.id] = defaultSerial;
                    }
                    // 当串口切换时
                    if (this.value_ !== self.serialType[this.sourceBlock_.id]) {
                        // 更新串口对象serialType
                        self.serialType[this.sourceBlock_.id] = this.value_;
                        // 更新联动的下拉框的默认值
                        if (self.serialType[this.sourceBlock_.id] === "Hardware Serial1") {
                            // setFieldValue: 参数1:默认值; 参数2:text中的对应参数
                            this.sourceBlock_.setFieldValue("GP1", "PINR")
                        } else {
                            this.sourceBlock_.setFieldValue('GP9', 'PINR')
                        }
                    }
                }
                // 返回下拉框列表
                return [
                    ["硬串口1", "Hardware Serial1"], // [显示文本, 实际value]
                    ["硬串口2", "Hardware Serial2"]
                ]
            }
        }
    }
    

    💚 提示: 1. setFieldValue 方法的第二个参数,必须和 block 对象中 arguments 参数中的属性名一致。 2. _getSerialTypeMenu 方法返回的串口下拉框列表中,默认值必须排第一个。

  3. 下拉框联动控制 (Rx 菜单):

    // 监听 Rx 的下拉框点击
    _getPINRMenu() {
        const self = this;
        return {
            items: function () {
                if (!this.sourceBlock_) return [['1', '1']];
                // 设置初始串口值
                if (!self.serialType.hasOwnProperty(this.sourceBlock_.id)) {
                    let defaultSerial = self.getCurrentSerialDefault(false);
                    self.serialType[this.sourceBlock_.id] = defaultSerial;
                }
                // 根据串口值返回不同的下拉框列表
                if (self.serialType[this.sourceBlock_.id] === "Hardware Serial1") {
                    return [["GP1", "GP1"]];
                } else {
                    return [["GP9", "GP9"]];
                }
            }
        }
    }
    

2. 输入校验规范

类型限制【仅上传模式支持】 * inputTypes: 指定拖入积木的返回类型【带输入框的积木】 * outputTypes: 指定积木的返回类型【圆形与菱形积木】

{
  opcode: 'getTarget',
  blockType: BlockType.REPORTER,
  text: '获取 [TARGET]',
  arguments: {
    TARGET: {
      inputTypes: [...DataType.NUMBER] // 允许数字类型输入
    }
  },
  outputTypes: [DataType.STRING] // 定义积木输出类型
}

DataType 可选参数数据类型

字符串值 说明
'int32_t' 数字类型
'uint32_t' 数字类型
'string' 字符串类型
'float' 浮点类型
'bool' 布尔类型
'void' 空类型
'void*' 空指针类型
'color' 颜色
'expression' 表情
'preview' 掌控的屏幕显示文字预览
'setting' 所有的设置下拉框

注意DataType.NUMBER 需要展开,写法如下: * inputTypes: [...DataType.NUMBER] * outputTypes: [...DataType.NUMBER]

积木 field 输入值正则校验

arguments: {
    ACCOUNT: {
        type: ArgumentType.STRING,
        defaultValue: 'yourSSID',
        inputTypes: [DataType.STRING],
        inputParams: { validate: this._validatePass },
    }
},
// 校验函数定义在类中
_validatePass(text) {
    var regx = /^[\x00-\x7e\u4e00-\u9fa5]{1,}$/;
    if (text && !regx.test(text)) {
        return null; // 校验失败
    }
    if (text === "") {
        return "yourSSID"; // 空值恢复默认
    }
    return text; // 校验通过
}

3. 积木 field 符号显示

  • symbol: 指定积木 field 显示特定符号,例如字符串输入框默认引号、列表默认中括号
arguments: {
    PATH: {
        type: ArgumentType.STRING,
        inputParams: {
            symbol: '""'    // 在输入框两端显示引号
        },
        defaultValue: "image.jpg"
    }
},

4. 扩展二级菜单

  • submenuId: 指定定义的二级菜单 。

注意二级菜单文字(json翻译文件中)不能有特殊字符(引号、&、<、>等),否则会导致积木无法显示。

blocks: [
    ////////////////////////////// 定义二级菜单 ///////////////////////////////
    {
        blockType: BlockType.SUBMENU,
        id: 'boardSensor',
        text: formatMessage({
            id: "deviceTemplate.submenu.boardSensor",
            default: 'board\'s sensor'
        }),
    },
    ////////////////////////////// 主程序 ///////////////////////////////
    {
        opcode: 'sensorBlock',
        blockType: BlockType.COMMAND,
        text: formatMessage({
            id: 'deviceTemplate.sensorBlock',
            default: 'This is a sensorBlock block',
        }),
        submenuId: "boardSensor", // 指定二级菜单
        arguments: {
        },
    },
]