跳转至

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

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

1. 扩展包文件结构

一个标准的扩展包目录结构如下:

extensionName/                  # 扩展库根目录
├── func.js                     # 生成代码 (原 opcodeFunc.js)
├── index.js                    # 图形块定义主文件
├── public/                     # 静态资源目录 (原 template/)
│   ├── cover.png               # 扩展封面图
│   ├── config.json             # 扩展配置文件(元数据)
│   ├── libraries/              # Python 扩展库文件目录 (可选)
│   │   └── df_xxx_lib.py       # Python 库文件
│   └── requirements.txt        # Python 项目依赖包 (可选)
├── icon/                       # 图标资源目录
│   ├── blockIcon.svg           # 积木图标 (建议使用白色)
│   └── menuIcon.svg            # 分类菜单图标 (建议使用积木颜色)
├── locales/                    # 国际化语言包
│   ├── zh-cn.json              # 简体中文翻译
│   ├── en.json                 # 英文翻译
│   └── index.js                # 翻译入口文件

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

config.json 是扩展的核心配置文件。

示例配置

{
    "id": "pinpongBase",
    "version": "0.0.1",
    "name": {
        "zh-cn": "pinpong初始化",
        "en": "pinpong initialize"
    },
    "description": {
        "zh-cn": "pinpong库初始化选择主控板功能和gpio",
        "en": "initialize pinpong library select board function and GPIO"
    },
    "author": "DFRobotTest",
    "cover": "cover.png",
    "main": "main.js",
    "isDevice": false,
    "mode": "python-block",
    "meta": {
        "runtimeVersion": "0.0.2"
    }
}

关键字段说明

字段 类型 说明
id string 扩展唯一标识符。
author string 与id一起组成扩展唯一标识。
version string 版本号。
name object 多语言名称。
description object 多语言描述。
cover string 封面图片文件名,需位于 public 目录下。
mode string 模式,此处应为 python-block
isDevice boolean Python模式均为 false
meta.runtimeVersion string 扩展 runtime 接口版本(用于控制 API 兼容性)。
compatibilityId string Mind+V1.x用户库兼容字段。

meta.runtimeVersion

runtimeVersion 表示开发扩展时依赖的 runtime API 最低版本。应按实际使用的 API 能力填写最小版本,避免随意填写过大导致扩展只能在高版本 Mind+ 中加载。

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. Python 依赖管理 (public/requirements.txt)

如果有配置,Mind+会在加载扩展时,在库管理中显示当前扩展的 Python 依赖库。

示例 requirements.txt:

flask==2.3.2
requests>=2.25.0
numpy<1.24.0

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

index.js 是扩展的入口文件。

类结构定义

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: "pinpong",
            blockIconURI: blockIconURI,
            menuIconURI: menuIconURI,
            color1: "#FF6680",
            color2: "#FF4D6A",
            color3: "#FF3355",
            blocks: [
                {
                    opcode: 'pinpongInit',
                    blockType: BlockType.COMMAND,
                    text: formatMessage({id: 'gui.extension.pinpongBase.pinpongInit', default: 'pinpong初始化'}),
                    arguments: {}
                }
            ],
            menus: {},
        };
    }
}

export default Extension;

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

func.js 负责将图形块转换为 Python 代码。

核心结构

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

    pinpongInit(generator, block, parameter) {
        // 生成 Python 代码逻辑
        // 示例:导入库
        generator.addImport('import time');
        return `print("Hello Python Mode")`;
    }
}

export default Func;

6. 翻译资源文件 (locales)

locales/zh-cn.json:

{
  "gui.extension.pinpongBase.pinpongInit": "pinpong初始化",
  "gui.extension.pinpongBase.pinpongUpdateFirmware": "pinpong更新固件 板型[Board]端口[Port]",
  "gui.extension.pinpongBase.pinInit": "[Instance]pin初始化 引脚号[Pin] 模式[Mod]",
  "gui.extension.pinpongBase.readPinValue": "[Instance]读数字值",
  "gui.extension.pinpongBase.setPinValue": "[Instance]设置数字输出 值[Value]",
  "gui.extension.pinpongBase.readAnalogValue": "[Instance]读模拟值",
  "gui.extension.pinpongBase.setAnalogValue": "[Instance]设置模拟输出(PWM) 值[Value]"
}

7. Python 生成代码 API

1. 导入库模块 addImport

addImport(code: string)

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

2. 添加全局变量 addVariable

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

  • 功能:定义全局变量
  • 参数
  • id - 变量字符串标识符
  • code - 实际生成代码
  • coverage - 是否覆盖同 id 变量的生成代码(默认 false
  • 示例
    generator.addVariable("myserial", `myserial = serial.Serial()`);
    
  • 生成效果
    myserial = serial.Serial()
    
    while True:
        pass
    

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

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

  • 功能:添加初始化的代码(相当于 Arduino 的 addSetup
  • 参数
  • id - 字符串标识符
  • code - 实际生成代码
  • priority - 优先级(0-9,9最靠前,默认 0)
  • isCover - 是否覆盖同 id 的生成代码(默认 false
  • 示例
    generator.addInit('aip_addUser_result', 'global aip_addUser_result', 1, true);
    
  • 生成效果
    global aip_addUser_result
    
    while True:
        pass
    

4. 添加全局函数 addFunction

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

  • 功能:添加全局函数
  • 参数
  • code - 函数内容生成代码(函数名作为 id)
  • isCover - 是否覆盖同 id 的生成代码(默认 false
  • 示例
    let funcCode = [
        `def send_to_serial(key, value):`,
        `    if myserial.isOpen():`,
        `        json_message = json.dumps({key: value})`,
        `        myserial.write(bytes(json_message, "utf-8"))`,
    ];
    generator.addFunction(funcCode.join('\n'), true);
    
  • 生成效果
    def send_to_serial(key, value):
        if myserial.isOpen():
            json_message = json.dumps({key: value})
            myserial.write(bytes(json_message, "utf-8"))
    
    while True:
        pass
    

5. 创建事件回调函数

  • 示例
    return (`def on_serial_message${index}_callback(msg):`);
    
  • 生成效果
    # 事件回调函数
    def on_serial_message0_callback(msg):
        pass
    
    while True:
        pass
    

6. 在积木位置生成 code

  • 位置while True: 或积木当前位置
  • 规则
  • 返回字符串直接插入代码
  • 返回数组 [code, priority] 处理优先级(如运算符顺序)
  • 示例
    return (`p_ens160.get_status()`);
    
  • 生成效果
    while True:
        # block返回的生成代码
        p_ens160.get_status()
    

7. 进阶用法

  • 添加注释 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}))`);
    }