# 烧录模式

烧录模式深入理解

如果你想继续深入理解烧录模式, 可以参照这里 深入理解 烧录模式 和 在线模式

uCode 的烧录模式分成两个部分:

  1. 代码烧录器 (Code Uploader) 简称: Uploader
  2. 代码转换器 (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

GeneratorBlockly 下的 CodeGenerator 非常类似, 不过由于 uCode 的原理稍微不太一样, 代码转换是直接由 UCD(uCode 项目文件) 直接转换而来, 也就是我们是从序列化之后的 积木块数据直接转成代码, 不需要经过 Blockly DOM 化的结构解析

为了尽可能简化学习成本, 大部分的使用和 Blockly 的写法类似.

Generator 总共有以下几种:

类型 对应的操作 说明
normal 普通类型积木块 普通类型积木块直接返回字符串
output 输出类型积木块 返回的代码必须以 数组
hat 帽子类型积木块 帽子积木块可以独立作为一个 function 或者一段声明的开始, 例如事件, 或者 Main 函数

# 内置的语言

目前支持两种语言:

  • Python
  • Arduino(C++) (USV 0.8.0 新增)

由于 Python 使用比较多, 下面都是以 Python 作为举例

# 普通类型积木块 案例

  1. 最简单的案例:

toCode 直接返回 字符串

const testProtocol = {
  /**
   * 积木块的参数会在 toCode 的第一个参数里面, 以 Key-Value 的对象
   */
  toCode(inputs) {
    return `testProtocolMsg(${inputs.TEXT})\n`;
  },
};
  1. 需要注入声明

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 判断是否需要增加括号
  1. 简单实例
const math_number = {
  toCode(args) {
    return [`${Cast.toNumberOrString(args.NUM)}`, ORDERS.ORDER_ATOMIC];
  },
};

Cast 工具说明见下面文档

  1. 需要对 arguments 的值配备优先级

类似 BlocklyvalueToCode(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 把积木块序列化之后, 并不区分 NumberString 类型, 因此需要使用者自行进行转换, 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)}`;

由于在前面添加了一个函数, 后面的代码, 都需要在每行的前面添加一个缩进

# 积木块转代码的示例

block png

积木块转代码:

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 需要替换的公共积木块代码转换器

CustomBlockGeneartorsCommonBlockGenerators 是以 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

  1. 首先先打开 Arduino IDE 的选项界面, 勾上 显示详细输出

  1. 点击编译按钮(确认各种参数是否正确)

  2. 然后在下方的详细输出第一行找到 -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,
});