# 烧录模式
烧录模式深入理解
如果你想继续深入理解烧录模式, 可以参照这里 深入理解 烧录模式 和 在线模式
uCode 的烧录模式分成两个部分:
- 代码烧录器 (Code Uploader) 简称: Uploader
- 代码转换器 (Code Generator) 简称: Generator
# 代码烧录器 Uploader
Uploader
主要用于烧录代码的处理相关逻辑, 由于各种硬件的烧录模式不尽相同, 后续我们会集成几种通用的烧录, 例如: Arduino 以及 ESP 之类
# Uploader 对应的接口方法
说到 Uploader
的接口规范,实际上就是硬件插件提供标准的接口,uCode 会在用户操作或者一些其他的设定,会触发这些接口
# Uploader 接口
接口 | 对应的操作 | 是否必须 | 是否阻塞 | 钩子 | 触发时机 |
---|---|---|---|---|---|
prepareEnv | 环境准备 | 否 | 是 | 无 | 在执行 runCode 或者 uploadCode 之前会触发该接口 |
uploadCode | 烧录代码 | 是 | 是 | beforeUploadCode afterUploadCode | 用户点击烧录代码的时候会触发该接口 |
runCode | 运行代码 | 否 | 是 | beforeRunCode afterRunCode | 用户点击运行代码的时候会触发该接口 |
switchMode | 模式切换 | 否 | 否 | 无 | 用户点击模式切换的时候触发该接口 |
interrupt | 中断 | 否 | 是 | 无 | 用在烧录代码或者运行代码, 环境准备 的时候, 点击强制关闭的时候触发该接口 |
destroy | 销毁操作 | 否 | 否 | 无 | 删除角色的时候会触发该接口 |
# Uploader 接口 API 文档
待补充
这里放链接
# 代码转换器 Generator
Generator 和 Blockly 下的 CodeGenerator 非常类似, 不过由于 uCode 的原理稍微不太一样, 代码转换是直接由 UCD(uCode 项目文件) 直接转换而来, 也就是我们是从序列化之后的 积木块数据直接转成代码, 不需要经过 Blockly DOM 化的结构解析
为了尽可能简化学习成本, 大部分的使用和 Blockly 的写法类似.
Generator 总共有以下几种:
类型 | 对应的操作 | 说明 |
---|---|---|
normal | 普通类型积木块 | 普通类型积木块直接返回字符串 |
output | 输出类型积木块 | 返回的代码必须以 数组 |
hat | 帽子类型积木块 | 帽子积木块可以独立作为一个 function 或者一段声明的开始, 例如事件, 或者 Main 函数 |
# 内置的语言
目前支持两种语言:
- Python
- Arduino(C++) (USV 0.8.0 新增)
由于 Python 使用比较多, 下面都是以 Python 作为举例
# 普通类型积木块 案例
- 最简单的案例:
toCode
直接返回 字符串
const testProtocol = {
/**
* 积木块的参数会在 toCode 的第一个参数里面, 以 Key-Value 的对象
*/
toCode(inputs) {
return `testProtocolMsg(${inputs.TEXT})\n`;
},
};
- 需要注入声明
toCode
需要返回 definitions
Key-Value 的对象
return {
code,
definitions,
};
const playTTSUntilDone = {
toCode(inputs) {
const definitions: { [key: string]: string } = {};
definitions["import_mini_base_api"] = "from mini.apis.base_api import MiniApiResultType";
definitions["import_mini_api_sound_play_tts_api"] = "from mini.apis.api_sound import StartPlayTTS";
let content = inputs.text || '"你好"';
let code = `await StartPlayTTS(is_serial=True, text=str(${content})).execute()\n`;
return {
code,
definitions,
};
},
};
# 输出积木块 Generator 案例
输出的积木块需要返回一个数组 [CODE, ORDER]
- CODE 是字符串, 也就是代码
- ORDER 是输出值的优先级, 用于 Generator 判断是否需要增加括号
- 简单实例
const math_number = {
toCode(args) {
return [`${Cast.toNumberOrString(args.NUM)}`, ORDERS.ORDER_ATOMIC];
},
};
Cast
工具说明见下面文档
- 需要对
arguments
的值配备优先级
类似 Blockly 的 valueToCode(argumentName, ORDER)
功能, 由于 uCode 的转代码是基于静态编译的, 因此要提前预设好, 如果不设置的话, 默认就是 ORDER_NONE
const operator_add = {
// 对 arguments 的数字配置优先级
arguments: {
NUM1: {
outerOrder: ORDERS.ORDER_ATOMIC,
},
NUM2: {
outerOrder: ORDERS.ORDER_ATOMIC,
},
},
toCode(args, constants) {
const num1 = Cast.toNumberOrString(args.NUM1) || 0;
const num2 = Cast.toNumberOrString(args.NUM2) || 0;
const code = `(${num1} + ${num2})`;
return [code, ORDERS.ORDER_ATOMIC];
},
};
# 帽子积木块 Generator 案例
帽子积木块有点特殊, 它不会像条件积木块一样, 把 statement 作为一个子积木块传递, 而是把后面的积木块作为一个 branchCode
的参数进行传递
// 帽子积木块 案例
const ON_PROGRAM_START = {
/**
* 帽子块必须显示声明为 hat: true
*/
hat: true,
/**
* 帽子块会有第三个参数, branchCode, 就是会把后面所有的积木块作为第三个参数传递
*/
toCode(inputs, constants, branchCode) {
const definitions: { [key: string]: string } = {};
definitions["import_mini_base"] = "import asyncio";
definitions["import_mini_sdk"] = "from mini import mini_sdk as MiniSdk";
let code = `async def on_start_event():\n${prefixLines(branchCode || 'pass', constants.INDENT)}`;
console.log(code);
return {
code,
definitions,
};
},
};
prefixLines
工具说明见下面文档
# Generator 参数说明
目前有两种 Generator
帽子块的 hat
要设为 true
toCode
有三个参数, 第三个 branchCode
会把后面的积木块的代码作为参数传入
type toCode = (args: {[key: string]: any}, constants: GeneratorConstants, branchCode: string): TocCodeResult
普通的积木块只有两个参数
type toCode = (args: {[key: string]: any}, constants: GeneratorConstants): TocCodeResult
# 工具函数说明
由于 Scratch 把积木块序列化之后, 并不区分 Number
和 String
类型, 因此需要使用者自行进行转换, Cast
也是 Scratch 内置的工具函数, uCode 进行了一定改造
例如: 上面的例子里面
import { CommonUtility } from '@ubtech/ucode-extension-common-sdk';
const Cast = CommonUtility.GeneratorUtil.Cast;
const num1 = Cast.toNumberOrString(args.NUM1);
Cast.toNumberOrString
如果转换 Number 失败, 会退回字符串, 而不是像 toNumber
那样 设成 0
, 这对于一些特殊场景特别有用
例如:
List
的添加, 默认是走字符串, 这使得需要数值运算特别困难- 如果含有的数值是变量, 则不需要转换
下面是函数的原注释, 如果用的是 TypeScript 会有注释提示:
Convert To Number, If Failed fallback to string value Example:
- "abc" => "abc"
- "123" => 123 Scratch List will convert the value as String no matter what you give,
list.append("123");
list.append("abc");
If use toNumber
will break the value:
list.append(123);
list.append(0);
The correct way is toNumberOrString
:
list.append(123);
list.append("abc");
在帽子块的案例, 有一个 prefixLines
的使用, 这个是用于给代码行添加前缀, 最经常使用的场景就是缩进
例如:
import { CommonUtility } from '@ubtech/ucode-extension-common-sdk';
const prefixLines = CommonUtility.GeneratorUtil.prefixLines;
let code = `async def on_start_event():\n${prefixLines(branchCode || 'pass', constants.INDENT)}`;
由于在前面添加了一个函数, 后面的代码, 都需要在每行的前面添加一个缩进
# 积木块转代码的示例
积木块转代码:
from mini.apis.base_api import MiniApiResultType
from mini.apis.api_sound import StartPlayTTS
import time
import asyncio
from mini import mini_sdk as MiniSdk
async def on_start_event():
await StartPlayTTS(is_serial=True, text=str("你好")).execute()
time.sleep((1 + 1) / 1)
await StartPlayTTS(is_serial=True, text=str("我是悟空")).execute()
# 注册器 Register
SDK 内置了一些常见的语言的生成器 (目前集成了 Python, 后面会陆续添加)
注册器需要提供三个:
需要提供 | 是否必须 | 说明 |
---|---|---|
uploader | 是 | 烧录器 |
CustomBlockGeneartors | 是 | 自定义积木块代码转换器 |
CommonBlockGenerators | 否 | 需要替换的公共积木块代码转换器 |
CustomBlockGeneartors
和 CommonBlockGenerators
是以 Key-Value
的对象定义, CustomBlockGenerators
的 key 需要和积木块定义的 opcode
相等
export const UploadModeRegister = PythonGenerator.getPythonCodeGenerators({
uploader: Uploader,
CustomBlockGeneartors: {
"test-protocol": {
toCode(args) {
return `testProtocolMsg(${args.TEXT})\n`;
},
},
},
CommonBlockGenerators: {
control_delay: {
toCode(args) {
return `await asyncio.sleep((1 + 1)/1))\n`;
},
},
},
});
# Arduino 支持
USV 0.8.0 新增 Arduino 语言的支持, 主要包括两个部分:
- Uploader 烧录支持
- Generator 语言支持
# Arduino Uploader
在 uCodeLink 内部我们集成了 Arduino-Cli Arduino 的工具链, 可以支持 Arduino 生态的编译以及烧录, 并且我们封装了一套使用的接口, 可以增加第三方库(仅限官方在线库), 检查库的版本
可以看以下案例:
import { UCodeLinkAPI, ErrorTool } from '@ubtech/ucode-extension-common-sdk';
/**
* 每个 Arduino 的编译器都有对应的 CoreName, 下面以 UNO 举例
*/
export const CoreName = 'arduino:avr:uno';
/**
* 下面是用于指定依赖的库, uCode 在编译前会先安装 (当前只能用于官方在线库)
*/
const arduinoOnlineLibraries: string[] = [
'LinkedList',
];
/**
* Arduino 编译工具使用前需要实例化
*/
const arduinoCompiler = new UCodeLinkAPI.ArduinoCompilerAdapter(CoreName);
/**
* 可以提前检查并安装在线库
*/
await arduinoCompiler.checkAndInstallRequireLibraries({
requireLibraries: arduinoOnlineLibraries,
});
/**
* 也可以直接在编译的代码的时候再检查
*/
arduinoCompiler.compile(code, port, (data) => verboseCallback?.(data), {
requireLibraries: arduinoOnlineLibraries,
});
SDK 内置了一个: ArduinoCompilerAdapter
, 实例化的时候需要提供一个 CoreName
如何查询 Arduino 开发板的 CoreName
- 首先先打开 Arduino IDE 的选项界面, 勾上
显示详细输出
点击编译按钮(确认各种参数是否正确)
然后在下方的详细输出第一行找到
-fqbn=xxx
的部分
例如这里是:
-fqbn=arduino:avr:uno
这里需要拿到的 CoreName
实际上是: arduino:avr:uno
有些板子分不同的 cpu
, 会有带有 cpu 的信息
例如 Mega 2560:
-fqbn=arduino:avr:mega:cpu=atmega2560
这里需要拿到的 CoreName
实际上是: arduino:avr:mega
不需要带上 cpu
等参数
SDK 可以支持检查安装在线 Arduino 在线第三方库 arduinoCompiler.checkAndInstallRequireLibraries({requireLibraries: arduinoOnlineLibraries});
获取第三方库的名字
库的名字不一定是 include
字段的名字, 不要混淆
#include <LinkedList.h>
可以通过菜单, 工具
-> 管理库
打开 Arduino IDE 的管理库界面
这里显示的才是安装库的名字
可以提前先检测再安装
const arduinoOnlineLibraries: string[] = [
'LinkedList',
];
arduinoCompiler.checkAndInstallRequireLibraries({
requireLibraries: arduinoOnlineLibraries,
});