# 积木块

# 积木块注册器类

废话不多说,我们现在开始

首先,积木块的注册器,需要你定义一个类,例如下方的:

ExampleDeviceBlockRegister

注册器主要包含两类成员方法:

  • getInfo()方法:提供积木块信息,以及颜色等一些全局设置和定义
  • [blockcallback]方法:与积木块 opcodefunc 字段同名的函数,作为图形化编程程序运行时的执行方法
Scratch 3 插件示例

Scratch3 的内置插件代码,你可以查阅这里: Scratch3 插件代码 (opens new window)


下面是完整的案例以及注释:

class ExampleDeviceBlockRegister {
  getInfo() {
    return [
      // 与 Scratch 插件不一样的是, 这里可以传入一个对象或者一个数组, 数组的话, 会创建多个分类, 但是要注意各个积木块, 以及 menu 的 opcode 或者 id 不要重复(重复会以最后一个覆盖)
      {
        name: "分类1", // toolbox 分类的名字
        iconURI: "", // icon 地址,链接地址或相对路径,或者 base64
        color1: "#5A9FFF", // 积木块主体填充色
        color2: "#4280D7", // 积木运行时的颜色,目前未启用
        color3: "#3373CC", // 积木块描边色
        blocks: [
          {
            blockType: self.UCode.BlockType.LABEL, // 设置子标题类型
            text: "子标题1", // 子标题名称
          },
          {
            opcode: "test-device", // 执行方法描述,也作为toolbox积木id, 因此必须唯一
            blockType: self.UCode.BlockType.COMMAND, // 积木类型
            arguments: {
              // 参数配置
              TEXT: {
                // 参数名, TEXT 和 text 字段中的 [TEXT] 是一一对应的,如果写错,会导致积木块无法渲染
                type: self.UCode.ArgumentType.STRING, // 参数类型
                defaultValue: "hello", // 参数默认值
              },
            },
            text: "测试 发送消息: [TEXT]", // 积木描述展示, [TEXT] 为参数占位符
            func: "testDeviceMsg", // 积木运行的方法名,需定义一个同名函数
          },
        ],
      },
      {
        name: "分类2", // toolbox 分类的名字
        iconURI: "", // icon 地址,链接地址或相对路径,或者 base64
        color1: "#5A9FFF", // 积木块主体填充色
        color2: "#4280D7", // 积木运行时的颜色,目前未启用
        color3: "#3373CC", // 积木块描边色
        blocks: [
          {
            opcode: "test-hat",
            blockType: self.UCode.BlockType.HAT,
            isEdgeActivated: true, // 帽子块可选配置,当配置为true时,表示为边缘触发,并且当该积木拖到工作区时会一直
            //轮询,当从false跳变到true时执行该积木下面的程序块
            arguments: {
              val: {
                type: self.UCode.ArgumentType.BOOLEAN,
              },
            },
            text: "当: [val]",
            func: "testHat",
          },
        ],
      },
    ];
  }

  // 积木的依赖 执行函数
  testDeviceMsg(args, util) {
    return new Promise((resolve, reject) => {
      const device = self.UCode.extensions.getDevice(util.targetId);
      console.log("Device", device, util);
      if (!device?.isConnected()) {
        console.log("Device 硬件没有连接");
        reject();
      } else {
        console.log("test-device", args.TEXT);
        device?.say(args.TEXT);
        resolve();
      }
    });
  }

  testHat(args, util) {
    return args.val;
  }
}

展示效果如下图所示:

extension展示效果

# 定义积木块

getInfo中,blocks数组每一项是积木块定义的配置。

blocks: [
  {
    blockType: self.UCode.BlockType.LABEL, // 设置子标题类型
    text: "子标题1", // 子标题名称
  },
  {
    opcode: "test-device", // 执行方法描述,也作为toolbox积木id, 因此必须唯一
    blockType: self.UCode.BlockType.COMMAND, // 积木类型
    hideFromPalette: false, // toolbox是否隐藏该积木
    arguments: {
      // 参数配置
      TEXT: {
        // 参数名, TEXT 和 text 字段中的 [TEXT] 是一一对应的,如果写错,会导致积木块无法渲染
        type: self.UCode.ArgumentType.STRING, // 参数类型
        defaultValue: "hello", // 参数默认值
      },
    },
    text: "测试 发送消息: [TEXT]", // 积木描述展示, [TEXT] 为参数占位符
    func: "testDeviceMsg", // 积木运行的方法名,需定义一个同名函数
  },
];

# 积木类型

关于更多 积木块 的定义或积木块生成工具(Blockly Developer Tools),可以查阅 Blockly 官方文档 (opens new window)

我们用blockType来声明积木块的类型以及外形。如blockType: self.UCode.BlockType.COMMAND

我们支持的 积木类型,以及其对应的参数。积木块的外形和积木块类型也有关系。

积木类型 积木样式图例 积木配置值 说明
帽子块 帽子块 self.UCode.BlockType.HAT 程序的开始块,积木块顶部不能连接其他积木块
执行块 执行块 self.UCode.BlockType.COMMAND 执行类积木
数值块 数值块 self.UCode.BlockType.NUMBER 返回一个数值
报告块 报告块 self.UCode.BlockType.REPORTER 返回一个值,勾选时会在舞台显示返回值
布尔块 布尔块 self.UCode.BlockType.BOOLEAN 返回一个布尔值,true or false
子标题 子标题 self.UCode.BlockType.LABEL 设置 toolbox 子标题

执行块或帽子块,还可以设置积木块底部是否允许被连接。不同的积木块类型,有不同的配置参数:

  • BlockType.HAT 类型:参数为hasNextStatement,默认为 undefined,即允许下方连接。设置为 false 时,积木块下方不可连接。
  • BlockType.COMMAND 类型:参数为isTerminal,默认为 undefined,即允许下方连接。设置为 true 时,积木块下方不可连接。

如下图所示,积木块底部是平的:

hasNextStatement

例:

{
  opcode: 'block-type-hat2',
  blockType: self.UCode.BlockType.HAT, // 帽子块/事件块
  text: '类型 帽子块截止块 (BlockType.HAT, hasNextStatement=false)',
  hasNextStatement: false, // 积木块底部设置为平的
  func: 'hatBlockFunc',
},
{
  opcode: 'block-type-command2',
  blockType: self.UCode.BlockType.COMMAND, // 执行块
  text: '类型 截止块 (BlockType.COMMAND, isTerminal=true)',
  isTerminal: true, // 积木块底部设置为平的
  func: 'commandBlockFunc',
},

# toolbox 隐藏积木

当新版本变更了积木时,为兼容旧版本作品文件,可在新版本将旧版本积木从 toolbox 隐藏,配置 hideFromPalettetrue

情况 1: 旧版本有积木 A,但新版本删除了积木 A,则在新版本将积木 A 设置 hideFromPalette 为 true

情况 2: 旧版本有积木 A,新版本需对该积木变更(描述,参数等变更),建议保留积木 A 并且配置 hideFromPalette 为 true;然后增加积木 B 为变更后的积木

# 参数控件(Field)类型

关于 参数控件(Field) 的定义,可以查阅 Blockly 官方文档 (opens new window)

我们用type来声明arguments中每个参数控件(Field)的类型。如

arguments: {
  // 参数配置
  TEXT: {
    // 参数名, TEXT 和 text 字段中的 [TEXT] 是一一对应的,如果写错,会导致积木块无法渲染
    type: self.UCode.ArgumentType.STRING, // 参数类型
    defaultValue: "hello", // 参数默认值
  },
},

对于报告块(即 self.UCode.BlockType.REPORTER),不能有参数(即不能配置 arguments),否则会不出现勾选框,无法在舞台显示

我们提供了一些 参数控件(Field)类型, 目前暂时不支持开放自定义,但我们会逐渐增多更多实用的参数,方便开发者更容易的实现需求

控件类型 控件交互 配置值 在积木块执行函数中,通过 args 获取到的值
数字输入盘 数字盘 self.UCode.ArgumentType.NUMBER_PAD 输入的数字
数字输入 键盘输入 self.UCode.ArgumentType.NUMBER_INPUT 输入的数字
字符串 键盘输入 self.UCode.ArgumentType.STRING 输入的字符串
布尔值 拖入一个布尔块 self.UCode.ArgumentType.BOOLEAN 布尔块的返回值
下拉菜单 下拉菜单 self.UCode.ArgumentType.DROPDOWN_MENU 选择的菜单值
菜单选择弹窗 菜单选择弹窗 self.UCode.ArgumentType.DIALOG_MENU 选择的菜单值
颜色 HSL 颜色 self.UCode.ArgumentType.COLOR #rrggbb 如:'#ff0000'
颜色 RGB 颜色 self.UCode.ArgumentType.RGB_COLOR #rrggbb 如:'#ff0000'
音符 音符 self.UCode.ArgumentType.NOTE MIDI note number
角度 角度 self.UCode.ArgumentType.ANGLE 角度值
普通点阵 点阵 self.UCode.ArgumentType.MATRIX 字符串,默认 5x5,用 25 个 0 或 1 表示 LED 亮灭
自定义点阵 点阵 self.UCode.ArgumentType.UCODE_CUSTOM_MATRIX 字符串,默认 8x8,用 8 个 0 或 1 表示 一行 LED 亮灭

参数控件(Field)类型 使用示例

  • 数字输入盘
// block声明
{
  opcode: "fieldTest",
  func: "fieldTest",
  blockType: self.UCode.BlockType.COMMAND,
  text: "数字键盘 [INPUT]",
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.NUMBER_PAD, // 数字输入盘
      defaultValue: 2, // 默认值
      options: { // 可选参数
        type: 'number',
        min: 1, // 最小值,非必填。若regExp值为空,则根据min的值来限制负号的输入。
        max: 100, //最大值,非必填
        precision: 1, //小数点位数限制,非必填。
        regExp: '^[-]?\\d+(.\\d{0,3})?$', // 校验规则,非必填。校验优先级高于【min,precision】的限制。
      }
    },
  },
},

// 执行函数
fieldTest(args) {
  console.log(args); // {mutation: undefined, INPUT: '2'} // INPUT参数值为"2"
  const input = Cast.toNumber(args.INPUT); // 转number类型。
}

Cast 可以这么导入:

import { CommonUtility } from "@ubtech/ucode-extension-common-sdk";
const { Cast } = CommonUtility;
// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.NUMBER_INPUT,
      defaultValue: 2,
      options: { // 可选参数
        type: 'number',
        min: 1, // 最小值,非必填。若regExp值为空,则根据min的值来限制负号的输入。
        max: 100, //最大值,非必填
        precision: 1, //小数点位数限制,非必填。
        regExp: '^[-]?\\d+(.\\d{0,3})?$', // 校验规则,非必填。校验优先级高于【min,precision】的限制。
      }
    },
  },
},

// 执行函数中第一个参数 args: {mutation: undefined, INPUT: '2'}
// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.STRING,
      defaultValue: "hello",
      options: { // 可选参数
        type: 'text',
        regExp: '^[a-zA-Z]{0,8}$', // 校验规则,非必填
      }
    },
  },
},

// 执行函数中第一个参数 args: {mutation: undefined, INPUT: 'hello'}
  • 布尔值
// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.BOOLEAN,
    },
  },
},

// 执行函数中第一个参数 args: {mutation: undefined, INPUT: false}。 积木块 input输入区 中未嵌入 boolean积木块,args中则不存在 INPUT
  • 下拉菜单
// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.DROPDOWN_MENU,
      menu: "fieldTestMenus",
      defaultValue: "1",
    },
  },
},

// menus定义
menus: {
  fieldTestMenus: {
    acceptReporters: false, // 是否可以嵌入其他积木块,false为否
    items: [
      {
        text: "菜单项1",
        value: "1",
      },
      {
        text: "菜单项2",
        value: "2",
      },
    ],
  },
},

// 执行函数中第一个参数 args: {mutation: undefined, INPUT: '1'}
  • 菜单选择弹窗
// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.DIALOG_MENU,
      menu: "fieldTestMenus",
      defaultValue: "1",
    },
  },
},

// menus定义
menus: {
  fieldTestMenus: {
    acceptReporters: false, // 是否可以嵌入其他积木块,false为否
    items: [
      {
        text: "菜单项1",
        value: "1",
      },
      {
        text: "菜单项2",
        value: "2",
      },
    ],
  },
},

// 执行函数中第一个参数 args: {mutation: undefined, INPUT: '1'}
  • 颜色 HSL
// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.COLOR,
      defaultValue: "#0000ff",
    },
  },
},

// 执行函数中第一个参数 args: {mutation: undefined, INPUT: '#22fd60'}
  • 颜色 RGB
// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.RGB_COLOR,
      defaultValue: "#0000ff",
    },
  },
},

// 执行函数中第一个参数 args: {mutation: undefined, INPUT: '#ff0000'}
  • 音符
// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.NOTE,
      defaultValue: 60,
    },
  },
},

// 执行函数中第一个参数 args: {mutation: undefined, INPUT: '60'}
  • 角度
// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.ANGLE,
      defaultValue: 90,
    },
  },
},

// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.MATRIX,
      defaultValue: "00100011100110001010001000101000100010100010001110",
      options: { // 可选参数,未设置时默认 5x5
        type: 'matrix';
        col: 10; // 10列
        row: 8; // 8行
      }
    },
  },
},

// block声明
{
  // ...省略...
  arguments: {
    INPUT: {
      type: self.UCode.ArgumentType.UCODE_CUSTOM_MATRIX,
      defaultValue: ['1000', '0001', '0011', '1011'],//4行4列
    },
  },
},

# 菜单配置

积木块下拉菜单

如果你想了解更多关于 Blockly 积木块下拉菜单的信息,可以查阅这里: Dropdown fields (opens new window)

当然,Scratch 3 插件的菜单定义方式,和 Blockly 下拉菜单定义方式是不同的,只是 scratch 的菜单最终也会转换为 blockly 下拉菜单

如果你想了解更多 Scratch 3 的下拉菜单定义,可以查阅 Scratch 内置插件Scratch3 插件代码 (opens new window),也就是 插件类--getInfo()方法--menus 字段

我们保留了和 Scratch 3 一样的菜单定义方式

blocks: [
 {
  opcode: "xxx",
  blockType: UCode.BlockType.COMMAND,
  text: "测试菜单: [MENU]",
  arguments: {
   MENU: {
    type: UCode.ArgumentType.DROPDOWN_MENU, // 菜单类型
    menu: 'testMenu' // 菜单名,需配置一个menus中存在的菜单名
    defaultValue: 1, // 默认值,应和testMenu菜单items的value对应
   },
  },
  func: "test",
 },
],
menus: {
  // 菜单名
  testMenu: {
    // 列表数组,text为下拉菜单或者uCode单选弹窗中展示值,value则为积木运行函数中通过args.MENU获取到的值
    items: [{ text: '菜单1', value: 1 }, { text: '菜单2', value: 2 }],},
  }
}

菜单项的类型声明如下:

type ScratchExtensionMenuItem = {
  acceptReporters?: boolean; // 是否允许嵌入积木块
  items:
    | string[] // 1、text-value同值
    | {
        // 2、text-value类型,适合做语言国际化翻译
        text: string;
        value: string | number;
      }[]
    | (() => [string, string][]); // 3、动态菜单类型,函数类型暂不支持
};

菜单如果按菜单数据的获取方式,可以分为两类:静态菜单、动态菜单。

  • 静态菜单:菜单子项在定义时就已经写好。

    testMenu: {
      items: [{ text: '菜单1', value: 1 }, { text: '菜单2', value: 2 }],},
    }
    
  • 动态菜单:菜单子项在运行时获取。items 是一个函数,返回菜单子项数组。一般用于从网络、设备中获取菜单列表。(暂不支持)

acceptReporters 字段:表示菜单是否可以嵌入其他积木块,如变量积木块。默认为 false,不允许被嵌套

pinValueMenus: {
  acceptReporters: false, // 是否可以嵌入其他积木块,false为否
  items: pinValueMenus,
},
pinValueMenus2: {
  acceptReporters: true, // 是否可以嵌入其他积木块,false为否
  items: pinValueMenus,
},

# 初始workspace积木配置

# 定义方式

在插件入口文件中,找到 register 配置,使用 DefaultTargetDataRegister 来配置初始workspace积木。

import type { UCodeExternalHardwareDefinition } from "@ubtech/ucode-extension-common-sdk/types";

const register: UCodeExternalHardwareDefinition.ExtensionRegister = {
  // ... 省略其他字段 ...
  // 配置初始workspace积木
DefaultTargetDataRegister: {
    "json": {
        "name": "all-features",
        "comments": {},
        "blocks": {
            "e@I:{^#w6Fil+!^Lc=|X": {
                "opcode": "deviceworker-bd4d8b65-b1dd-48e7-a441-5226599ab563_block-type-hat",
                "next": null,
                "parent": null,
                "inputs": {},
                "fields": {},
                "shadow": false,
                "deletable": true,
                "topLevel": true,
                "x": 457,
                "y": 469
            }
        },
        "variables": {},
        "lists": {},
        "broadcasts": {}
    },
  }
}

# 参数说明

DefaultTargetDataRegister类型说明如下:

interface AssetDataType = {
  base64: string;
  string: string;
  uint8array: Uint8Array;
  json: any;
}
type DefaultTargetData =
  | {
      json: {
        name: string; //插件名
        layerOrder?: number;
        blocks: { 
          opcode: string;
          next: string | null;
          parent: string | null;
          inputs: { [key: string]: SerializeInputsResult };
          fields: { [key: string]: SerializeField };
          shadow: boolean;
          deletable: boolean;
          topLevel?: boolean;
          x?: number;
          y?: number;
          mutation?: HTMLElement;
          comment?: string;
        } | (string | number)[];
        variables: { [key: string]: (string | boolean | number)[] };
        lists:{ [key: string]: [string, string[] | number[] | boolean[]] };
        broadcasts: { [key: string]: string };
        comments:  {
          blockId: string;
          x: number;
          y: number;
          width: number;
          height: number;
          minimized: boolean;
          text: string;
        };
        ucode?: {
          assets?: <T extends keyof AssetDataType = keyof AssetDataType>{
            assetId: string;
            name: string;
            md5ext: string;
            dataFormat: string;
            assetType: CustomAssetType;
            extraData?: UBT.KeyValueOf;
            data?: AssetDataType[T];
          };
        };
      };
    }
  | undefined;

# 获取初始workspace积木数据的方式

首先把需要设置为初始workspace积木块拖到工作区,然后在uCode的开发者工具的Console中输入如下js代码:

(window as any).getCurrentTargetBlockData();

# 积木块执行函数

在积木块定义中,funcopcode字段声明了执行函数名,在插件类中需要定义同名函数。

示例
class ExampleDeviceBlockRegister {
  getInfo() {
    return {
      name: "案例硬件", // toolbox 分类的名字,如果不提供这个名字,默认会从 manifest.json 的 name 获取
      blocks: [
        {
          opcode: "test-opcode", // 执行方法描述(执行函数名称),也作为toolbox积木id, 因此必须唯一
          blockType: self.UCode.BlockType.COMMAND, // 积木类型
          arguments: {
            // 参数配置
            TEXT: {
              // 参数名, TEXT 和 text 字段中的 [TEXT] 是一一对应的,如果写错,会导致积木块无法渲染
              type: self.UCode.ArgumentType.STRING, // 参数类型
              defaultValue: "hello", // 参数默认值
            },
          },
          text: "测试 发送消息: [TEXT]", // 积木描述展示, [TEXT] 为参数占位符
          func: "blockFunc", // 积木运行的方法名(执行函数名称),需定义一个同名函数。不同积木块可以复用同一个func名称,即复用执行函数。
        },
      ],
    };
  }

  // 积木的执行函数
  blockFunc(args, util) {}
}

# 参数

每个执行函数,会注入两个参数:

  • args 对应积木块参数的值。可以在 getInfo()--block 定义中,arguments 字段中找到相应的值。
  • util 注入的上下文
blockFunc(args, util) {}

这里 util 里面可以获取到当前的 targetId 也就是当前运行的这么积木块所属的角色

# 返回值类型及其作用

  • Promise 类型:实现阻塞模式(积木块模拟同步代码),积木块运行后,直到 resolve()或 reject(),才算完成积木块运行,进入下一个积木块程序。
示例
blockFunc(args, util) {
  return Promise((resolve) => {
    setTimeout(() => {
      resolve(); // 积木块会执行3秒
    }, 3000);
  })
}
  • void 类型:积木块运行后,程序进入下一个积木块的执行函数中。
示例
blockFunc(args, util) {
  console.log('do something');
}

blockFunc1(args, util) {
  const condition = true;
  if (condition) {
    return;
  }
  console.log('do something');
}
  • 基本数据类型:返回布尔/数值/字符串等,可以提供给其他积木块使用。一般用于BOOLEANNUMBERREPORTER类型的积木块。
示例
blockFunc(args, util) {
  return 25;
}

# 帽子块 执行函数

帽子块是HAT类型积木块,也就是设置了blockType: self.UCode.BlockType.HAT参数的积木块,它的运行方式和其他类型的积木块不同,所以单独说明一下。

帽子块存在的意义,是作为一个分支的起始块,当条件符合时,自动触发,运行该分支的程序。适合做一些事件监听任务。如:

hat

当帽子块参数isEdgeActivated设置为 true 时,这类积木块拖到 工作区 后,执行函数会被立即执行。

  • 当条件不满足时,执行频率和 uCode 虚拟运行环境(VM)执行频率一致(约每秒 30 次)。相当于轮询检测条件是否满足执行函数代码设定。(因此在 帽子块 执行函数中,不能有耗时操作、异步操作,容易影响性能。)
  • 当条件满足,执行函数执行完毕,进入下一个积木块执行函数,或者帽子块被删除,执行函数停止轮询。

hat

# 配置参数

在定义帽子块时,可以设置以下参数:

  • isEdgeActivated: boolean 设置边缘触发?默认 false,目前需要显式设置为 true。
    • 设置为 true,当状态改变时,如 true -> false 或 false -> true ,执行该分支程序。分支程序执行完一遍后,回到帽子块,如果条件没有跳变,不会再次触发分支程序。
    • 如果设置为 false,需要手动调用 startHats 触发分支执行 (startHats方法尚未开放)
  • isConditionActivated: boolean 设置条件触发?默认 false。
    • 当设置为 true 时,当条件满足时执行该分支程序,比如分支执行完一遍后,回到帽子块执行函数,轮询条件,发现是 true,又执行分支程序。
  • shouldRestartExistingThreads: boolean 设置是否重置所有分支?默认 false。
    • 当设置为 true 时,事件触发时,会打断其他分支的执行。
    • 当设置为 false 时,不影响其他分支执行。

例:

{
  opcode: 'hat',
  func: 'hat',
  blockType: self.UCode.BlockType.HAT,
  isEdgeActivated: true, // 边缘触发
  shouldRestartExistingThreads: true, // 打断其他分支程序
  text: '帽子块',
  arguments: {
    TEXT: {
      type: self.UCode.ArgumentType.STRING,
      defaultValue: '',
    },
  },
},

# 返回值类型

帽子块的执行函数,需要 boolean 值作为判断数据。

返回值类型:

  • boolean
  • Promise<boolean>

不考虑边缘触发还是条件触发的模式下:

  • 当判断结果为 true 时,运行帽子块后面的积木块,帽子块执行函数运行完成。完成后需要等待分支全部执行完成,才会进入下一次的监听。
  • 当判断结果为 false 时,不执行后面的积木块,继续轮询帽子块条件。

例:

  _updateStatus(status: boolean) {
    this._status = status; // 更新状态
  }

  // return boolean
  hat() { // 帽子块执行函数
    console.log('hat block execute function');
    return this._status; // 轮询状态
  }

  // async-await
  async hat() {
    console.log('hat block execute function');
    return await Promise.resolve(true);
  }

  // Promise
  hat() {
    console.log('hat block execute function');
    return new Promise((resolve) => {
      setTimeout((resolve) => {
        resolve(true);
      }, 3000);
    });
  }

# 积木块执行函数中使用 硬件设备

由于硬件的协议实例化,也是由 uCode 进行实例化管理和调度,因此,如果需要在积木块里面使用对应的角色协议,可以通过以下方式获取:

testDeviceMsg(args, util) {
  // 通过targetId拿到当前角色绑定的 device 对象。该对象可以发送、接受数据
  const device = self.UCode.extensions.getDevice(util.targetId);
}
关于硬件设备

点击查阅硬件设备介绍。

如果你现在还想了解更多 硬件设备 API,可以查阅这里: API 文档

Target 角色的概念

角色的概念可以查阅介绍里面的 面向角色编程

如果是 硬件角色 ,则它还会绑定一个 硬件设备 也就是 Device 的实例化对象,他们是一一对应的关系

因此如果拿到了 Target ID 也就是 角色 的唯一标识符,就可以获取到对应的 硬件设备

完整的 device 获取,可以参照下面的案例代码



 












testDeviceMsg(args, util) {
  return new Promise((resolve, reject) => {
    const device = self.UCode.extensions.getDevice(util.targetId);
    console.log('HardwareDevice', device, util);
    if (!device?.isConnected()) {
      console.log('HardwareDevice 硬件没有连接');
      reject();
    } else {
      console.log('test-device', args.TEXT);
      device?.say(args.TEXT);
      resolve();
    }
  });
}

HardwareDevice 有可能为空

HardwareDevice 如果没有连接的情况下,是会空的,需要开发者自己处理。 也可以使用 Optional Channing 简化操作 例如: device?.isConnected()

# 积木块 注册原理

积木块遵循 先定义,再使用 的原则

Scratch 的积木块定义也是基于 Blockly 基础上二次开发的,Scratch 的插件积木块定义方式,使用了 Blockly 提供的 JSON 格式定义 (opens new window)

我们也一样,使用了 JSON 格式定义,最终会翻译成下面类似的格式:

{
  "type": "string_length",
  "message0": "length of %1",
  "args0": [
    {
      "type": "input_value",
      "name": "VALUE",
      "check": "String"
    }
  ],
  "output": "Number",
  "colour": 160,
  "tooltip": "Returns number of letters in the provided text.",
  "helpUrl": "http://www.w3schools.com/jsref/jsref_length_string.asp"
}