跳转至

Mind+ MicroPython 积木模式扩展开发手册

本文档将指导开发者创建适用于 Mind+ MicroPython 积木模式的扩展。


1. 扩展包文件结构

Mind+ V2 扩展库基于模板仓库 mindplus-ext2-builder 开发,克隆或下载该仓库得到的目录既是开发工程,也是最终提交收录的仓库,结构如下:

ext2-mpy-name/                              # 仓库名称可参考,以ext2-mpy-为前缀
├── extension/                              # 扩展库源码目录
│   ├── func.js                             # 生成代码
│   ├── index.js                            # 图形块定义主文件
│   ├── public/                             # 静态资源目录
│   │   ├── cover.png                       # 封面图
│   │   ├── config.json                     # 配置文件
│   │   └── libraries/                      # MicroPython 扩展库文件目录 (可选)
│   │       └── df_xxx_lib.py               # MicroPython 库文件
│   ├── icon/                               # 图标资源目录
│   │   ├── blockIcon.svg                   # 积木图标 (建议使用白色)
│   │   └── menuIcon.svg                    # 分类菜单图标 (建议使用积木颜色)
│   └── locales/                            # 国际化语言包
│       ├── zh-cn.json                      # 简体中文翻译
│       ├── en.json                         # 英文翻译
│       └── index.js                        # 翻译入口文件 (构建时自动生成)
├── scripts/                                             
├── utils/                                                   
├── ...               
└── README.md

extension/ 及 README.md 需要自己编写,其余部分是固定的构建环境,无需修改


2. 配置文件 (public/config.json)

config.json 是扩展的核心配置文件,定义了扩展的基本信息、支持的设备以及依赖库。

示例配置

{
  "id": "DFR0024",
  "author": "yourUserId",
  "version": "0.0.1",
  "name": {
    "zh-cn": "DS18B20温度传感器",
    "en": "DS18B20 Temperature Sensor"
  },
  "description": {
    "zh-cn": "测量温度,范围-55~+125℃",
    "en": "Detect ambient temperature with large range of -55~+125℃"
  },
  "cover": "cover.png",
  "isDevice": false,
  "mode": "micropython-block",
  "main": "main.js",
  "supportDevices": {
    "dev-DFRobot-handpyEsp32": "^0.0.1",
    "dev-DFRobot-unihikerK10": "^0.0.1"
  },
  "meta": {
    "runtimeVersion": "0.0.1"
  },
  "sku": "DFR0024",
  "compatibilityId": ""
}

关键字段说明

字段 必填 类型 说明
id 必填 string 扩展唯一标识符,建议仅包含字母和数字
author 必填 string 作者 ID
version 必填 string 扩展版本号
name 必填 object 多语言名称
description 必填 object 多语言描述
cover 必填 string 封面图片文件名,需位于 public 目录下
isDevice 必填 boolean 模块扩展固定为 false
mode 必填 string 模式,此处应为 micropython-block
main 必填 string 入口文件路径,一般为编译后的 main.js
supportDevices 必填 object 支持的主控板及版本要求
meta.runtimeVersion 必填 string 扩展 runtime 接口版本(用于控制 API 兼容性)
sku 可选 string 硬件产品型号,多个型号用|隔开;没有则留空
compatibilityId 可选 string Mind+V1.x用户库兼容字段;没有则留空

不需要使用可选字段时,sku、compatibilityId 写空字符串:

{
  ...
  "sku": "",
  "compatibilityId": ""
}

meta.runtimeVersion

runtimeVersion 表示开发扩展时依赖的 runtime API 最低版本,以 通用参考-Runtime接口参考 为准

不可随意设置一个很大的版本号,否则扩展只能在高版本中加载,兼容性会变差

compatibilityId(仅需兼容V1.x库时使用)

如果当前开发的库由Mind+V1.x库转换而来,或者需要在Mind+V2.x中打开Mind+V1.x包含同样用户库的项目文件,则可以使用此字段指定V1.x用户库的id。

注意:如需兼容V1.x版本扩展,需要保证V2扩展库index.js中积木的opcode与V1.x版本扩展库main.ts中对应积木的函数名称一致,并保证积木类型、数量、输入框数量和类型、兼容主板等信息保持一致,并在Mind+V1版本中加载旧扩展的所有积木保存项目,然后在Mind+V2中需要保证可以正常打开。

参考示例:
- V1用户库:https://gitee.com/liliang9693/ext-ds1302
- V2用户库:https://gitee.com/mind-plus/ext2-ds1302

格式:"compatibilityId": "[author]-[id]-thirdex"

例如:

  • 在V1的扩展库中config.json中,"id": "oled12864","author": "DFRobot",则在V2的扩展库中的config.json中设置 "compatibilityId": "dfrobot-oled12864-thirdex"
  • 另外也可以直接查看V1导出的.mpext文件名,例如 dfrobot-oled12864-thirdex-V0.0.3.mpext,不带版本号和后缀即为compatibilityId的值;或者也可以在Mind+V2中打开包含这个库的V1的项目文件,会提示“项目中存在尚未兼容的扩展: dfrobot-oled12864-thirdex”,提示语中的字段即为compatibilityId的值。

3. 图形块描述文件 (index.js)

index.js 是扩展的入口文件,负责定义图形块(Block)的外观、参数以及与 Mind+ 运行时的交互。

类结构定义

模块扩展通过 runtime.getCurrentDeviceId() 获取当前主控标识,可按当前主控动态调整积木菜单。

import ArgumentType from '../utils/argument-type';
import BlockType from '../utils/block-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: 'SEN0001',
            blockIconURI: blockIconURI,
            menuIconURI: menuIconURI,
            color1: '#FF9F06',
            color2: '#DD8A04',
            color3: '#BE7705',
            blocks: [
                {
                    opcode: 'readUltrasound',
                    blockType: BlockType.REPORTER,
                    text: formatMessage({
                        id: 'gui.blocklyText.sen0001.readUltrasound',
                        description: 'Label for the blocklyText of init',
                        default: 'ulrasonic sensor unit init'
                    }),
                    arguments: {}
                }
            ],
            menus: {},
        };
    }
}

export default Extension;

关键方法说明

方法 作用 是否必须
getCodePrimitives() 返回积木代码生成方法对象(Func 实例) 必须
getInfo() 返回扩展积木元信息(名称、图标、颜色、blocks、menus) 必须
setLocale(locale) 响应 IDE 语言切换 必须

4. 代码生成文件 (func.js)

func.js 负责将图形块转换为 MicroPython 代码。函数名必须与 index.js 中积木的 opcode 一一对应。

核心结构

class Func {
    constructor(runtime, extensionId) {
        this.runtime = runtime;
    }

    readUltrasound(generator, block, parameter) {
        // generator: 代码生成器,提供 addImport / addInit 等 API
        // block: 积木实例
        // parameter: 积木参数,通过 parameter.XXX.code 取值
        generator.addImport('import time');
    }
}

export default Func;

5. 翻译资源文件 (locales)

locales/zh-cn.json:

{
  "gui.blocklyText.sen0001.readUltrasound": "超声波传感器初始化"
}

6. MicroPython 生成代码 API

在 func.js 的 opcode 方法中,通过 generator 调用以下 API。

1. 导入库模块 addImport

addImport(code: string)

  • 功能:导入 MicroPython 库模块
  • 参数:code — 实际生成代码(去掉空格后作为 id)
  • 覆盖规则:相同 id 时覆盖旧代码
  • 示例:
    generator.addImport('from mpython import *');
    generator.addImport('import time');
    
  • 生成效果:
    from mpython import *
    import time
    
    while True:
        pass
    

2. 添加全局变量 addVariable

addVariable(id: string, code: string, coverage: boolean = false)

  • 功能:定义全局变量
  • 参数:
  • id — 变量标识符
  • code — 实际生成代码
  • coverage — 是否覆盖同 id 变量(默认 false)
  • 示例:
    generator.addVariable('tim12', 'tim12 = Timer(12)');
    
  • 生成效果:
    tim12 = Timer(12)
    
    while True:
        pass
    

3. 添加初始化区域代码 addInit

addInit(id: string, code: string, priority: number = 0, isCover: boolean = false)

  • 功能:添加初始化代码(位于 while True: 之前)
  • 参数:
  • id — 标识符
  • code — 实际生成代码
  • priority — 优先级(0–9,9 最靠前,默认 0)
  • isCover — 是否覆盖同 id 代码(默认 false)
  • 示例:
    generator.addInit('myUI', 'myUI = UI(oled)', 1, true);
    
  • 生成效果:
    myUI = UI(oled)
    
    while True:
        pass
    

4. 添加全局函数 addFunction

addFunction(code: string, isCover: boolean = false)

  • 功能:添加全局函数(函数名作为 id)
  • 参数:
  • code — 完整函数代码
  • isCover — 是否覆盖同名函数(默认 false)
  • 示例:
    const funcCode = [
        'def display_font(_font, _str, _x, _y, _wrap, _z=0):',
        '    _start = _x',
        '    for _c in _str:',
        '        _d = _font.get_ch(_c)',
        '        _x += _d[2]'
    ];
    generator.addFunction(funcCode.join('\n'));
    
  • 生成效果:
    def display_font(_font, _str, _x, _y, _wrap, _z=0):
      _start = _x
      for _c in _str:
          _d = _font.get_ch(_c)
          _x += _d[2]
    
    while True:
        pass
    

5. 添加 while True 头部代码 addWhile

Mind+V2.0.8及以上版本可用

addWhile(id: string, code: string, isCover: boolean = false)

  • 功能:在 while True: 循环头部插入代码
  • 参数:
  • id — 标识符
  • code — 实际生成代码
  • isCover — 是否覆盖同 id 代码(默认 false)
  • 示例:
    generator.addWhile(`next(func_${buttonStr})`, `next(func_${buttonStr})`, true);
    
  • 生成效果:
    while True:
        next(func_button_a)
    

6. 创建事件回调函数

  • 返回以 def 开头的函数定义字符串,作为事件积木回调:
    return (`def on_${attitude}():`);
    
  • 生成效果:
    def on_shaked():
        pass
    
    while True:
        pass
    

7. 在积木位置生成 code

  • 位置:while True: 或积木当前位置
  • 规则:
  • 返回字符串直接插入代码
  • 返回数组 [code, priority] 处理运算符优先级
  • 示例:
    return 'magnetic.calibrate()';
    // 或
    return [`dht11.temperature()`, generator.ORDER_ATOMIC];
    
  • 生成效果:
    while True:
        magnetic.calibrate()
    

8. 获取已生成的初始化代码 getSetup

getSetup(id: string)

  • 功能:获取指定 id 的初始化区域代码,常用于合并中断触发条件等场景
const lastInitCode = generator.getSetup(`button_${button}.irq`);
if (lastInitCode && lastInitCode.indexOf(mode) === -1) {
    // 已有另一沿的中断配置,合并为双边沿
}

9. 获取已生成的静态函数 getStaticFunction

getStaticFunction(funcName: string)

  • 功能:获取已生成的同名函数代码,便于在多个积木间复用 / 合并定时器回调等逻辑
const lastFuncCode = generator.getStaticFunction('timer13_tick');
if (lastFuncCode && lastFuncCode.indexOf('dht11') === -1) {
    // 扩展已有定时器回调
}

10. 进阶用法

  • 添加注释 addComment(id: string, comment: any, position: number, isCover: boolean)
  • 添加错误提示 addErrorPrompt(message: string)
  • 获取资源路径 get_asset_path_by_name(生成代码中可用的全局函数)
createImageReaderFromFile(generator, block, parameter) {
    generator.addImport('from model_mp_io import ImageReader');
    const reader = parameter.READER.code;
    const path = parameter.PATH.code;
    return `${reader} = ImageReader(source=get_asset_path_by_name(${path}))`;
}