Mind+ 上传模式扩展开发手册¶
本文档将指导开发者创建适用于 Mind+ 上传模式(Arduino/Micropython)的硬件扩展。
1. 扩展包文件结构¶
一个标准的扩展包目录结构如下:
extensionName/ # 扩展库根目录
├── func.js # 生成代码
├── index.js # 图形块定义主文件
├── public/ # 静态资源目录
│ ├── cover.png # 扩展封面图
│ └── config.json # 扩展配置文件(元数据)
│ ├── libraries/ # Arduino 库文件目录 (可选)
│ │ └── DFRobot_xxxx # Arduino 库文件
│ │ │ └── library.properties # 库配置文件
├── icon/ # 图标资源目录
│ ├── blockIcon.svg # 积木图标
│ └── menuIcon.svg # 分类菜单图标
├── locales/ # 国际化语言包
│ ├── zh-cn.json # 简体中文翻译
│ ├── en.json # 英文翻译
│ └── index.js # 翻译入口文件
2. 配置文件 (public/config.json)¶
config.json 是扩展的核心配置文件,定义了扩展的基本信息、支持的设备以及依赖库。
示例配置¶
{
"id": "test",
"author": "yourUserId",
"mode": "upload",
"version": "0.0.1",
"name": {
"zh-cn": "I2C级联扩展器",
"en": "I2C cascade extender"
},
"description": {
"zh-cn": "用于解决I2C器件地址的冲突",
"en": "Used to resolve I2C device address conflicts"
},
"cover": "cover.png",
"isDevice": false,
"main": "main.js",
"libraryConfig": ["DFRobot_Mindplus_Multiplexer@1.0.0"],
"supportDevices": {
"dev-DFRobot-dfrobotKitRob0135": "^0.0.1",
"dev-DFRobot-arduinoUno": "^0.0.1",
"dev-DFRobot-microbitV2": "^0.0.1"
},
"supportArch": ["arduino:avr"],
"meta": {
"runtimeVersion": "0.0.2"
}
}
关键字段说明¶
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 扩展唯一标识符,仅包含字母和数字,勿使用空格或特殊符号。 |
author | string | 开发者名称,仅包含字母和数字,勿使用空格或特殊符号,与id一起组成扩展唯一标识。。 |
version | string | 扩展版本号,三段数字形式。 |
name | object | 扩展库卡片标题,需多语言支持。 |
description | object | 扩展库卡片描述,需多语言支持。 |
cover | string | 封面图片文件名,需位于 public 目录下。 |
isDevice | boolean | false 表示这是一个功能扩展,true 表示这是主控板扩展。 |
libraryConfig | array | 依赖的 Arduino 库列表,格式为 库名称@版本号。 |
supportDevices | object | 支持的主控板列表及其版本要求。 |
supportArch | array | (可选)仅 upload 模式:支持的主板架构列表。如 ["arduino:avr","arduino:samd"],配置后会覆盖 supportDevices。谨慎设置,一般使用指定主板方式设置兼容。 |
mode | string | 模式,此处应为 upload。 |
meta.runtimeVersion | string | 扩展 runtime 接口版本(用于控制 API 兼容性)。 |
meta.runtimeVersion¶
runtimeVersion 表示开发扩展时依赖的 runtime API 最低版本。比如 runtime.getBoardPinsByType 在 0.0.2 才支持,则使用该 API 的扩展不能在 runtime 0.0.1 的 Mind+ 中加载;但也不应随意设置一个很大的版本号,否则扩展只能在高版本中加载,兼容性会变差。
supportArch(仅 upload + isDevice: false)¶
supportArch 用于按“主板架构”声明兼容范围,方便小模块扩展一次适配一类主板,而不是枚举所有 supportDevices。
- 架构取自主板
boardConfig.board的前两段:例如"arduino:avr:uno"的架构为"arduino:avr" - 当
supportArch存在时,会覆盖supportDevices - 谨慎设置,一般使用指定主板方式设置兼容。
3. 图形块描述文件 (index.js)¶
index.js 是扩展的入口文件,负责定义图形块(Block)的外观、参数以及与 Mind+ 运行时的交互。
类结构定义¶
import ArgumentType from '../utils/argument-type';
import BlockType from '../utils/block-type';
import DataType from '../utils/data-type';
import blockIconURI from './icon/blockIcon.svg';
import menuIconURI from './icon/menuIcon.svg';
import Func from './func'; // 引入生成代码类
import {setLocaleData, formatMessage, setLocale} from '../utils/translation';
import LocaleData from "./locales";
// 注册翻译数据
setLocaleData(LocaleData);
class Extension {
constructor(runtime, extensionId) {
this.runtime = runtime;
// 初始化生成代码的实例
this.funcs = new Func(runtime, extensionId);
}
// 切换语言的钩子
setLocale(locale) {
setLocale(locale);
}
// 必须实现:返回生成代码的实例
getCodePrimitives() {
return this.funcs;
}
// 必须实现:返回扩展的详细信息
getInfo() {
return {
name: formatMessage({id:"ext.dfr0576.name", default:"I2C Cascade Extender"}),
blockIconURI: blockIconURI,
menuIconURI: menuIconURI,
blockIconWidth: 36,
blockIconHeight: 36,
color1: "#EB2F96", // 主色
color2: "#C41D7F", // 边框色
color3: "#C41D7F", // 阴影色
blocks: [
{
opcode: 'begin', // 对应 func.js 中的函数名
blockType: BlockType.COMMAND,
text: formatMessage({
id: "gui.blocklyText.dfr0576.begin",
default: 'Initialize [ADDR]'
}),
arguments: {
ADDR: {
type: ArgumentType.STRING,
menu: 'iicAddrMenu',
defaultValue: '0x70'
}
}
}
],
menus: {
iicAddrMenu: {
items: [
{ text: '0x70', value: '0x70' },
{ text: '0x71', value: '0x71' }
]
}
}
};
}
}
export default Extension;
关键方法说明¶
getCodePrimitives(): 必须。返回包含生成代码逻辑的对象实例(即Func类的实例)。getInfo(): 必须。定义扩展的元数据,包括名称、图标、颜色、积木列表(blocks)和菜单(menus)。setLocale(locale): 必须。用于响应Mind+设置中的切换语言操作。
4. 代码生成文件 (func.js)¶
func.js 负责将图形块转换为实际的 Arduino C++ 代码。
核心结构¶
class Func {
constructor() {
}
// 对应 index.js 中定义的 opcode: 'begin'
begin(generator, block, parameter) {
let addr = parameter.ADDR.code;
// 添加头文件引用
generator.addInclude('DFRobot_I2CMultiplexer.h');
// 添加对象定义
generator.addObject('I2CMulti', `DFRobot_I2CMultiplexer I2CMulti;`);
// 添加 Setup 中的初始化代码
// 第4个参数 true 表示即使有多个相同积木,也只生成一次
generator.addSetup(`I2CMulti.begin`, `I2CMulti.begin(${addr});`, 0, true);
// 返回 Loop 中的执行代码
return `I2CMulti.begin(${addr});`;
}
// 对应 index.js 中定义的 opcode: 'selectPort'
selectPort(generator, block, parameter) {
let port = parameter.PORT.code;
generator.addInclude('DFRobot_I2CMultiplexer.h');
generator.addObject('I2CMulti', `DFRobot_I2CMultiplexer I2CMulti;`);
// 如果 setup 代码是固定的,可以直接传入字符串
generator.addSetup(`I2CMulti.begin`, `I2CMulti.begin(0x70);`);
return `I2CMulti.selectPort(${port});`;
}
}
export default Func;
Generator API 常用方法¶
1. 生成头文件 addInclude¶
addInclude(id: string, code?: string)
- 功能:插入/覆盖头文件
- 参数:
id- 头文件标识符(如<MPython.h>)code- 实际生成代码(可选,默认使用#include <id>)- 覆盖规则:相同
id时覆盖旧代码 - 示例:
- 生成效果:
2. 生成 define 形式的宏 addDefine¶
addDefine(name: string, value: string, cover: boolean = false)
- 功能:生成
#define宏 - 参数:
name- 宏名称(作为唯一ID)value- 宏值cover- 是否覆盖同名宏(默认false)- 示例:
- 生成效果:
3. 创建全局对象 addObject¶
addObject(id: string, code: string, cover: boolean = false)
- 功能:声明全局对象(如
Servo servo;) - 参数:
id- 对象类型标识符(如Servo)code- 实际声明代码cover- 是否覆盖同名对象(默认false)- 示例:
- 生成效果:
4. 创建全局变量 addVariable¶
addVariable(name: string, type: 'number' | 'string' | 'list'): string
- 功能:声明全局变量,自动生成前缀
- 参数:
name- 变量名(作为ID)type- 类型决定前缀:number→volatile float mind_n_<name>string→String mind_s_<name>list→SimpleList<String> mind_l_<name>
- 返回:生成的变量名
- 示例:
const varName = generator.addVariable("my_float_variable", 'number'); // 生成: volatile float mind_n_my_float_variable; //const varName = generator.addVariable("my_string_variable", 'string'); // 生成: String mind_s_my_string_variable; //const varName = generator.addVariable("my_list", 'list'); // 生成: SimpleList<String> mind_l_my_list; - 生成效果:
5. 创建静态常量 addConst¶
addConst(name: string, type: string, content: string, isArray: boolean = true, cover: boolean = false): string
- 功能:声明常量,支持数组自动后缀
- 参数:
name- 常量名(同名不同内容时自动追加_1)type- 常量类型(如uint8_t)content- 初始化值isArray- 是否为数组(默认true,生成name[])cover- 是否覆盖(默认false)- 返回:实际常量名
- 示例:
// 方法1 普通类型常量,效果:const uint8_t led = 5; const ledName = generator.addConst("led", "uint8_t", "5", false); // 方法2 数组类型常量,效果:const uint8_t bbcBitmap[] = {B01010,B10101,B10001,B01010,B00100}; const bitmapName = generator.addConst("bbcBitmap", "uint8_t", "{B01010,B10101,B10001,B01010,B00100}"); // name 相同、content 不同,效果:const uint8_t bbcBitmap_1[] = {B00100,B00010,B11111,B00010,B00100}; const bitmapName1 = generator.addConst("bbcBitmap", "uint8_t", "{B00100,B00010,B11111,B00010,B00100}"); - 生成效果:
6. 创建静态函数 addFunction¶
addFunction(code: string, cover: boolean = false)
- 功能:插入函数声明/定义(函数名作为ID)
- 参数:
code- 完整函数代码cover- 是否覆盖同名函数(默认false)- 示例:
- 生成效果:
7. 在 setup 中生成 code¶
addSetup(id: string, code: string, priority: number = 0, cover: boolean = false)
- 功能:向
setup()函数插入代码 - 参数:
id- 唯一标识符code- 实际代码priority- 优先级(0-9,9最靠前,默认 0)cover- 是否覆盖(默认false)- 示例:
- 生成效果:
8. 在积木位置生成 code (Loop中)¶
- 位置:
loop()或积木当前位置 - 规则:
- 返回字符串直接插入代码
- 返回数组
[code, priority]处理优先级(如运算符顺序) - 示例:
- 生成效果:
9. 进阶用法¶
- 添加注释
addComment(id: string, comment: any, position: number, coverage: boolean) - 添加错误提示
addErrorPrompt(message: string) - 重写内置积木执行方法:
5. 翻译资源文件 (locales)¶
使用 locales/index.js 统一管理多语言资源。
locales/index.js:
// 自动加载该目录下所有 json 文件
const req = require.context('./', true, /\.json$/);
const locales = {};
req.keys().forEach((key) => {
const name = key.match(/\/([\w-]+)\.json$/)[1];
locales[name] = req(key);
});
export default locales;
locales/zh-cn.json:
{
"ext.dfr0576.name": "级联扩展器",
"gui.blocklyText.dfr0576.begin": "初始化I2C级联模块地址为 [ADDR]",
"gui.blocklyText.dfr0576.selectPort": "选通端口 [PORT]"
}
6. 引脚规范(仅上传模式支持)¶
目的:将小模块与主板解耦。主板通过统一规范暴露引脚能力,小模块通过 runtime API 获取并构建积木,从而减少小模块为适配不同主板而频繁更新的问题。
6.1 引脚类型(features)¶
- 模拟
PWM: 模拟输出,占空比变化的高低电平输出(timer 定时器,硬件 PWM)ADC: 模拟输入DAC: 模拟输出,连续变化的模拟电压- 数字
DIGITAL_INDIGITAL_OUT- I2C
I2C_SDA: 数据I2C_SCL: 时钟- SPI
SPI_MOSI: 主 -> 从SPI_MISO: 从 -> 主SPI_SCK: 时钟SPI_CS: 片选- UART
UART_TX: 发送UART_RX: 接收- 中断
EXT_INTERRUPT: 外部中断引脚(软串口的 RX 引脚需要)- 可触摸的金手指
TOUCH_PAD- 内部外设占用
RESERVED: 内部外设占用(点阵、传感器等)
说明:按钮/LED 这类板载外设通常也作为 GPIO 开放给用户;但板载传感器、点阵等开放出去会造成困扰的外设,可以标记为 RESERVED。
6.2 主板暴露引脚(Device.getPins)¶
主板扩展的 device.js 实现 getPins(),返回主板引脚列表与能力描述:
class Device {
getPins() {
return [
{
features: [
"UART_TX", "UART_RX",
"EXT_INTERRUPT",
"DIGITAL_IN", "DIGITAL_OUT",
"ADC", "PWM"
],
text: "P0",
value: "P0"
},
];
}
}
6.3 获取引脚(runtime.getBoardPinsByType)¶
小模块通过 runtime API 获取当前主板满足某类能力的引脚列表,并构建积木下拉框。
版本要求:meta.runtimeVersion >= 0.0.2
使用限制:仅上传模式支持该 API
6.3.1 设备参数传递¶
主板在初始化过程中需要调用 getInfo(),但此时主板未成功注册,runtime 可能无法获取 device 对象,所以需要手动传入 device 对象。
// 主板扩展:初始化阶段需手动传入 device
runtime.getBoardPinsByType("DIGITAL_OUT", this.device);
// 小模块扩展:直接调用,runtime 会自动获取当前主板 device
runtime.getBoardPinsByType("DIGITAL_OUT");
6.3.2 type 类型参数说明¶
| type 值 | 过滤条件 | 说明 |
|---|---|---|
ALL | 无 | 返回所有引脚,不做任何过滤 |
DIGITAL_READ | DIGITAL_IN 且非 RESERVED | 数字输入引脚 |
DIGITAL_WRITE | DIGITAL_OUT 且非 RESERVED | 数字输出引脚 |
ANALOG_READ | ADC 且非 RESERVED | 模拟输入引脚 |
ANALOG_WRITE | PWM 或 DAC 且非 RESERVED | 模拟输出引脚 |
| 自定义类型 | 指定 type | 如 I2C_SDA、I2C_SCL、UART_TX、UART_RX、EXT_INTERRUPT 等 |
说明: - 四种常见场景(DIGITAL_READ/DIGITAL_WRITE/ANALOG_READ/ANALOG_WRITE)会自动过滤 RESERVED(内部外设占用)引脚 - 自定义类型(如 I2C/UART/EXT_INTERRUPT)不会过滤 RESERVED,按实际能力返回
6.3.3 返回值示例¶
// 获取所有引脚
runtime.getBoardPinsByType("ALL", this.device);
// 返回: [{ features: ["UART_TX", "UART_RX", ...], text: "P0", value: "P0" }, ...]
// 获取数字输入引脚(排除内部占用)
runtime.getBoardPinsByType("DIGITAL_READ", this.device);
// 返回: [{ features: ["DIGITAL_IN", "DIGITAL_OUT", ...], text: "P0", value: "P0" }, ...]
// 获取 PWM/模拟输出引脚
runtime.getBoardPinsByType("ANALOG_WRITE", this.device);
// 返回: [{ features: ["PWM", ...], text: "P0", value: "P0" }, ...]
// 获取 I2C 引脚(不排除 RESERVED)
runtime.getBoardPinsByType("I2C_SDA", this.device);