# 积木块
# 积木块注册器类
废话不多说,我们现在开始
首先,积木块的注册器,需要你定义一个类,例如下方的:
ExampleDeviceBlockRegister
注册器主要包含两类成员方法:
- getInfo()方法:提供积木块信息,以及颜色等一些全局设置和定义
- [blockcallback]方法:与积木块
opcode
或func
字段同名的函数,作为图形化编程程序运行时的执行方法
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;
}
}
展示效果如下图所示:
# 定义积木块
在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 时,积木块下方不可连接。
如下图所示,积木块底部是平的:
例:
{
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 隐藏,配置 hideFromPalette
为 true
情况 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();
# 积木块执行函数
在积木块定义中,func
或opcode
字段声明了执行函数名,在插件类中需要定义同名函数。
示例
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');
}
- 基本数据类型:返回布尔/数值/字符串等,可以提供给其他积木块使用。一般用于
BOOLEAN
、NUMBER
、REPORTER
类型的积木块。
示例
blockFunc(args, util) {
return 25;
}
# 帽子块 执行函数
帽子块是HAT类型
积木块,也就是设置了blockType: self.UCode.BlockType.HAT
参数的积木块,它的运行方式和其他类型的积木块不同,所以单独说明一下。
帽子块存在的意义,是作为一个分支的起始块,当条件符合时,自动触发,运行该分支的程序。适合做一些事件监听任务。如:
当帽子块参数isEdgeActivated
设置为 true 时,这类积木块拖到 工作区
后,执行函数会被立即执行。
- 当条件不满足时,执行频率和 uCode 虚拟运行环境(VM)执行频率一致(约每秒 30 次)。相当于轮询检测条件是否满足执行函数代码设定。(因此在 帽子块 执行函数中,不能有耗时操作、异步操作,容易影响性能。)
- 当条件满足,执行函数执行完毕,进入下一个积木块执行函数,或者帽子块被删除,执行函数停止轮询。
# 配置参数
在定义帽子块时,可以设置以下参数:
- 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);
}
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"
}