一、定位和创意

ppio 是一个自己设计的命令行工具,用于简洁高效地切换 PlatformIO 项目中的 main.cpp,并自动编译 + 上传。

适用场景:教学实验、比赛设计、小型多项目管理,需要在多个 main.cpp 之间快速切换。

二、功能总结

  • 支持 .cpp 文件作为目标。

  • 支持 .ino 文件,自动重命名为 .cpp

  • 支持目录名,自动搜索同名 .ino/.cpp

  • 自动处理子目录时搜索 PlatformIO 根目录

  • 如果指定文件已是 src/main.cpp,则自动跳过 copy

  • 支持 PlatformIO 命令参数(如 -t upload, -e esp32s3 等)

  • 方便 Arduino 项目和 PlatformIO 项目互相转换

  • 支持 -c 命令:将 .cpp 文件转换成 .ino

三、支持的命令格式

【基础命令】

//ppio xxxx 是直接编译并上传
ppio xxx.cpp             # 作为新的 main.cpp  
ppio xxx.ino             # 自动改名为 xxx.cpp 并替换 main
ppio xxx_folder/         # 自动搜索 xxx_folder/xxx_folder.ino 或 .cpp

【PlatformIO 自定义扩展命令】

ppio -t upload xxx.cpp       # 指定 upload 目标
ppio run xxx.cpp             # 统一 PlatformIO run 运行

【转 Arduino 项目 .ino】

ppio -c blink.cpp            # 生成 blink_convert.ino
ppio -c blink_folder/        # 搜索 blink_folder/blink_folder.cpp 转为 .ino

【帮助提示】

ppio                        # 打印命令用法

四、与其他多工程管理方法对比

方法描述优缺点
修改 platformio.ini 中 src_dir每次切换项目会修改 ini 配置❌ 引起重新编译全部库,耗时,容易出错
#include 切换 mainmain.cpp 中通过 include 切换不同实验❌ 进入重复、字符冲突、调试困难
多 project 目录每个项目都是一个完整 PIO 工程❌ 无法共享 lib,文件重复,管理维护累人
✅ ppio 工具保留给 PlatformIO 的结构不变,快速替换 main.cpp✅ 极速切换,零副作用,适合教学、比赛和小型项目管理

五、自己打包成 EXE

  1. 安装 PyInstaller

pip install pyinstaller
  1. 打包指令

pyinstaller --onefile --name ppio ppio.py
  1. 结果 生成路径:dist/ppio.exe

  2. (可选) 如果不想要黑窗口,可加:

--noconsole

但对 CLI 工具不推荐使用,无法看日志

六、全局使用

方案 1: 把 ppio.exe 放入环境变量 PATH 中

例如:

D:\Tools\ppio\ppio.exe

然后把 D:\Tools\ppio\ 加入 Windows 环境变量 PATH,任何系统终端都可以调用:

ppio xxx.cpp

方案 2: 当前目录使用

./ppio.exe xxx.cpp

七、结论

ppio 是一个优雅、简洁、用习非常直观的小工具,实现了 PlatformIO 小型多项目快速切换、Arduino 项目互换、后缀适配和命令扩展。

它适合教学实验、比赛项目、基础课程实操等场景,帮助用户高效率管理实验程序,轻松快捷。

快速下载链接:

百度网盘下载

Github下载

八、更新日志

  • v1.0 (基础版)

    • 支持 .cpp 文件切换 main.cpp

    • 支持 pio run/上传 命令传递

  • v1.1

    • 支持 .ino 文件,自动 rename 为 .cpp

    • 支持输入目录,搜索同名 .ino/.cpp

    • 支持 tab 补全的结尾 “\” 或 “//” 删除

  • v1.2

    • 增加 -c 模式:.cpp 转 .ino,便于转回 Arduino

    • 支持目录/文件名与同名文件匹配

  • v1.3

    • 如果目标已经是 main.cpp,自动 skip copy 操作

    • 优化不同情况下的错误提示,更好用

九、附录:源码

import sys
import os
import shutil
import subprocess

def find_project_root(start_path):
    current = os.path.abspath(start_path)
    while True:
        if os.path.exists(os.path.join(current, "platformio.ini")):
            return current
        parent = os.path.dirname(current)
        if parent == current:
            return None
        current = parent

def resolve_target(arg):
    arg = arg.rstrip("/\\")  # 修复 tab 自动补全路径结尾斜杠问题

    if os.path.isdir(arg):
        folder_name = os.path.basename(arg)
        ino = os.path.join(arg, folder_name + ".ino")
        cpp = os.path.join(arg, folder_name + ".cpp")
        if os.path.exists(ino):
            print(f"[INFO] Found INO file: {ino}")
            new_cpp = ino.replace(".ino", ".cpp")
            os.rename(ino, new_cpp)
            print(f"[INFO] Renamed INO to CPP: {new_cpp}")
            return new_cpp
        elif os.path.exists(cpp):
            print(f"[INFO] Found CPP file: {cpp}")
            return cpp
        else:
            print(f"[ERROR] No .ino or .cpp file found in folder {arg}")
            return None
    elif arg.endswith(".ino"):
        if not os.path.exists(arg):
            print(f"[ERROR] File not found: {arg}")
            return None
        new_cpp = arg.replace(".ino", ".cpp")
        os.rename(arg, new_cpp)
        print(f"[INFO] Renamed INO to CPP: {new_cpp}")
        return new_cpp
    elif arg.endswith(".cpp"):
        if not os.path.exists(arg):
            print(f"[ERROR] File not found: {arg}")
            return None
        return arg
    else:
        print(f"[ERROR] Unsupported file or directory: {arg}")
        return None

def convert_to_ino(path):
    path = path.rstrip("/\\")
    if os.path.isfile(path) and path.endswith(".cpp"):
        base = os.path.splitext(path)[0]
        dest = base + "_convert.ino"
        shutil.copyfile(path, dest)
        print(f"[OK] Converted: {path} -> {dest}")
    elif os.path.isdir(path):
        folder_name = os.path.basename(path)
        cpp = os.path.join(path, folder_name + ".cpp")
        if os.path.exists(cpp):
            dest = os.path.join(path, folder_name + "_convert.ino")
            shutil.copyfile(cpp, dest)
            print(f"[OK] Converted: {cpp} -> {dest}")
        else:
            print(f"[WARN] No matching {folder_name}.cpp found in folder {path}")
    else:
        print(f"[ERROR] Not a valid path: {path}")

def main():
    args = sys.argv[1:]
    if not args:
        print("\nUsage:")
        print("  ppio main1.cpp")
        print("  ppio blink.ino                (auto rename to .cpp)")
        print("  ppio main333                  (looks for main333/main333.ino or .cpp)")
        print("  ppio -c sketch.cpp            (convert .cpp → .ino)")
        print("  ppio -c folder_name/          (convert folder_name/folder_name.cpp → _convert.ino)")
        return

    # handle -c: convert cpp to ino
    if args[0] == "-c":
        for path in args[1:]:
            convert_to_ino(path)
        return

    cmd = ["pio"]
    target = None

    for arg in args:
        if arg.endswith(".cpp") or arg.endswith(".ino") or os.path.isdir(arg):
            target = resolve_target(arg)
        else:
            cmd.append(arg)

    if target is None:
        print("[ERROR] No valid .cpp/.ino file specified.")
        return

    project_root = find_project_root(os.getcwd())
    if project_root is None:
        print("[ERROR] Cannot find PlatformIO project root (missing platformio.ini).")
        return

    main_cpp_path = os.path.join(project_root, "src", "main.cpp")

    print(f"\n[INFO] Preparing to copy {target} to {main_cpp_path}...")
    os.makedirs(os.path.dirname(main_cpp_path), exist_ok=True)

    if os.path.abspath(target) != os.path.abspath(main_cpp_path):
        shutil.copyfile(target, main_cpp_path)
        print(f"[OK] main.cpp is now: {target}")
    else:
        print(f"[SKIP] Source and destination are the same file. Skipping copy.")

    if len(cmd) == 1:
        cmd = ["pio", "run", "-t", "upload"]

    print(f"\n[INFO] Running: {' '.join(cmd)}")
    subprocess.run(cmd, cwd=project_root)

if __name__ == "__main__":
    main()
Logo

更多推荐