跳转至

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。
sku 可选 string 硬件产品型号,多个型号用|隔开;没有则留空,即写成 "sku": ""。
meta.runtimeVersion 必填 string 扩展 runtime 接口版本(用于控制 API 兼容性)。
compatibilityId 可选 string Mind+V1.x用户库兼容字段;没有则留空,即写成 "compatibilityId": ""。

注:sku 和 compatibilityId 为可选字段,不需要填写时请保留为空字符串,即写成 "sku": "" 和 "compatibilityId": ""。

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}))`);
    }