English | 简体中文
ulanzistudio-plugin-sdk-python 是 UlanziStudio 插件 SDK 的 Python 版本。它封装了与 UlanziStudio 上位机之间的 WebSocket 连接和事件协议,让 Python 主服务可以用简单的事件回调接收上位机消息,并发送图标、参数、Settings、弹窗等命令。
本 SDK 的事件与方法面参考
UlanziTechnology/plugin-common-node,协议版本对应 Ulanzi JS 插件开发协议 - V3.1.0。
manifest.json 配置参考:manifest.zh.md。
src/ulanzi_api/
├── constants.py # 事件名常量
├── random_port.py # 为自建主服务生成随机端口并写入 ws-port.js
├── utils.py # 插件路径、系统类型、JSON 解析等工具方法
├── ulanzi_api.py # SDK 主类,封装 WebSocket 与事件收发
└── __init__.py # 导出 UlanziApi、Utils、RandomPort、Events
pip install websocket-client如果直接把本仓库放到插件运行环境中使用:
pip install .导入方式:
from ulanzi_api import UlanziApi, Utils, RandomPort- Python 主服务,例如
app.py,应始终与 UlanziStudio 保持连接,负责插件核心逻辑、接收 action 参数变更并更新图标状态。 - Action / 配置项 HTML 页面应保持轻量,只负责配置 UI 和参数交换。
- 插件包命名规则:
com.ulanzi.{插件名}.ulanziPlugin - 主服务 UUID 必须恰好包含 4 个点分隔片段:
com.ulanzi.ulanzistudio.{插件名} - Action UUID 必须超过 4 个片段:
com.ulanzi.ulanzistudio.{插件名}.{actionName} - 当配置项页面需要绕过 UlanziStudio,直接连接插件自己的 Python 主服务时,使用
RandomPort为自建服务生成随机监听端口,并写入ws-port.js供配置项页面读取;普通通过 UlanziStudio 通信的插件不需要使用。 - 使用
Utils.getPluginPath()获取以ulanziPlugin结尾的插件根目录。
同一个 action 可以被分配到多个按键,所以 SDK 会为每个按键实例生成唯一的 context,并附加到收到的消息中。
- 格式:
uuid + '___' + key + '___' + actionid - 编码:
UD.encodeContext(message) - 解码:
UD.decodeContext(context)返回{"uuid": ..., "key": ..., "actionid": ...} - 对
clear事件,context会被写入message["param"]数组的每一项。
RandomPort 适用于配置项页面需要自己连接主服务的场景:例如 Python 主服务在本地自建 WebSocket / HTTP 服务,配置项页面需要绕过 UlanziStudio 中转,直接与这个服务交换连接状态、账号信息、实时列表或其他临时数据。此时主服务启动时先调用一次 getPort(),用返回的端口启动自己的服务;RandomPort 会同时把端口写入插件根目录的 ws-port.js,配置项 HTML 页面引入该文件后即可读取 window.__port 并发起连接。
如果配置项与主服务之间的参数同步完全通过 UlanziStudio 的标准事件完成,则不需要使用 RandomPort。
from ulanzi_api import RandomPort
random_port = RandomPort()
port = random_port.getPort() # 生成端口并写入插件根目录 ws-port.js
# 使用这个端口启动插件自己的本地 WebSocket / HTTP 服务,供配置项页面直连
# start_service(host="127.0.0.1", port=port)在配置项 HTML 中,连接前先引入生成文件:
<script src="../../ws-port.js"></script>
<script>
const socket = new WebSocket(`ws://127.0.0.1:${window.__port}`);
</script>注意事项:
RandomPort只负责生成端口并写入ws-port.js,不会自动创建 WebSocket / HTTP 服务。- 请在主服务启动阶段调用一次
getPort(),并在服务监听成功后再让配置项页面连接。 - 默认端口范围为动态端口区间
49152-65535,用于降低不同插件之间的端口冲突概率;如需限制范围,可通过RandomPort(min_port=..., max_port=...)自定义。
由上位机启动时,Python 连接参数从 sys.argv 读取:
sys.argv[1]-> 地址,默认127.0.0.1sys.argv[2]-> 端口,默认3906sys.argv[3]-> 语言,默认en
from ulanzi_api import UlanziApi
UD = UlanziApi()
def connected(_):
print("已连接")
def add(message):
context = message["context"]
print("Action 已添加:", context)
def run(message):
UD.setStateIcon(message["context"], 1, "ON")
def param_from_app(message):
print("已保存参数:", message.get("param"))
UD.onConnected(connected)
UD.onAdd(add)
UD.onRun(run)
UD.onParamFromApp(param_from_app)
UD.connect("com.ulanzi.ulanzistudio.myplugin")
UD.wait()connect() 默认启动后台 WebSocket 线程。主服务里可调用 wait() 保持进程运行,也可以传 threaded=False 在当前线程运行 WebSocket 循环。
连接事件:
UD.onConnected(lambda message: None)
UD.onClose(lambda message: None)
UD.onError(lambda error: None)按键事件:
UD.onAdd(lambda message: None)
UD.onRun(lambda message: None)
UD.onKeyDown(lambda message: None)
UD.onKeyUp(lambda message: None)
UD.onSetActive(lambda message: None)
UD.onClear(lambda message: None)旋钮 / 编码器事件:
UD.onDialDown(lambda message: None)
UD.onDialUp(lambda message: None)
UD.onDialRotate(lambda message: None)
UD.onDialRotateLeft(lambda message: None)
UD.onDialRotateRight(lambda message: None)
UD.onDialRotateHoldLeft(lambda message: None)
UD.onDialRotateHoldRight(lambda message: None)参数、Settings、跨页面通信与对话框事件:
UD.onParamFromApp(lambda message: None)
UD.onParamFromPlugin(lambda message: None)
UD.onDidReceiveSettings(lambda message: None)
UD.onDidReceiveGlobalSettings(lambda message: None)
UD.onSendToPlugin(lambda message: None)
UD.onSendToPropertyInspector(lambda message: None)
UD.onSelectdialog(lambda message: None)同时提供 Python 风格别名,例如 on_connected、on_run、set_state_icon、send_param_from_plugin。
设置按键图标:
UD.setStateIcon(context, state, text=None)
UD.setBaseDataIcon(context, data, text=None)
UD.setPathIcon(context, path, text=None)
UD.setGifDataIcon(context, gifdata, text=None)
UD.setGifPathIcon(context, gifpath, text=None)V3.1 显示内容命令(要求 UlanziStudio 3.3.0 或更高版本):
UD.setState(context, 1)
UD.setImage(
context,
{
"isDefault": True,
"icons": [
{"state": 0, "source": "path", "path": "images/off.png"},
{"state": 1, "source": "base64", "base64": "data:image/png;base64,..."},
],
},
)
UD.setImage(
context,
{"isDefault": False, "icons": {"source": "path", "path": "images/result.png"}},
)
UD.setImage(context, {"clear": True})
UD.setTitle(context, "就绪")每个 icons 项支持 path、base64、state、clear 四种 source;省略 source 时默认使用 path。
旋钮反馈:
UD.setFeedbackLayout(context, "$UA1")
UD.setFeedback(
context,
{"title": {"text": "就绪"}, "icon": {"value": "Images/new.png"}},
)只支持内置布局标识。V3.1.0 协议将自定义布局文件标为“待实现”,因此公共库不会封装该分支。
发送参数和透传数据:
UD.sendParamFromPlugin(settings, context=None)
UD.sendToPropertyInspector(settings, context)
UD.sendToPlugin(settings)Settings 持久化:
UD.setSettings(settings, context=None)
UD.getSettings(context=None)
UD.setGlobalSettings(settings, context=None)
UD.getGlobalSettings(context=None)系统功能:
UD.toast(msg)
UD.showAlert(context=None)
UD.logMessage(msg, level="info")
UD.hotkey(key)
UD.openUrl(url, local=False, param=None)
UD.openView(url, width=200, height=200, x=None, y=None, param=None)
UD.selectFileDialog(file_filter=None)
UD.selectFolderDialog()Utils 是从 ulanzi_api 导出的单例。
Utils.getPluginPath()
Utils.getSystemType() # "windows" 或 "mac"
Utils.adaptLanguage("zh-CN") # "zh_CN"
Utils.parseJson('{"a": 1}')
Utils.debounce(fn, wait=150)
Utils.getProperty(obj, "list[0].name", default_value=None)启动 Ulanzi Studio 时可附加以下参数开启调试。
| 参数 | 说明 |
|---|---|
--log |
将日志写入文件 |
--logLevel |
设置日志级别 |
--pluginLoad |
开启插件加载钩子 |
--webRemoteDebug |
启用 HTML 插件的 WebView 远程调试,默认端口 9292 |
--webRemotePort=<端口> |
自定义 WebView 调试端口 |
--nodeRemoteDebug |
启用 Node.js 远程调试;插件同时包含 Node 服务时可用 |
--doubleClick |
启用双击检测 |
Windows 快捷方式目标示例:
"C:\...\Ulanzi Studio.exe" --log --webRemoteDebug
macOS:
open /Applications/Ulanzi\ Studio.app --args --log --webRemoteDebug