FlexDesigner SDK
用于开发 FlexDesigner 插件的综合 SDK,提供与 Flexbar 设备的无缝集成和丰富的交互功能。
安装
前置要求
- Node.js 版本 20 或更高
- FlexDesigner 版本 1.0.0 或更高。
设置
将 FlexDesigner SDK 添加到您的项目
在典型情况下,您只需要使用
flexcli创建插件项目,FlexDesigner SDK 将自动安装。
npm install @eniactech/flexdesigner-sdk快速开始
const { plugin, logger, pluginPath, resourcesPath } = require("@eniactech/flexdesigner-sdk")
// Start the plugin
plugin.start()
// Handle plugin events
plugin.on('plugin.alive', (payload) => {
logger.info('Plugin loaded:', payload)
})
plugin.on('plugin.data', (payload) => {
logger.info('User interaction:', payload)
return 'Response from plugin backend!'
})核心概念
插件生命周期
- 初始化:插件启动并连接到 FlexDesigner
- 活跃事件:当按键加载并准备就绪时触发
- 数据事件:当用户与按键交互时触发
- 配置:插件可以读取/写入配置数据
按键类型
- 标准按键:基本按钮功能
- 多状态按键:在不同状态之间循环
- 滑块按键:连续值调整
- 动态按键:动态管理的按键集合
- 滚轮按键:旋转编码器支持
API 参考
内置模块
为了简化插件开发过程,我们预集成了几个可以直接使用的模块:
如果您需要集成其他模块,请告诉我们
核心插件 API
plugin.start()
启动 WebSocket 连接并初始化插件。
plugin.start()plugin.on(event, handler)
为特定事件注册事件处理器。
参数:
event(string):事件类型handler(function):事件处理函数
plugin.on('plugin.data', (payload) => {
const { data, serialNumber } = payload
logger.info('Key pressed:', data.key.title)
return { status: 'success', message: 'Key handled!' }
})plugin.off(event)
注销事件处理器。
plugin.off('plugin.data')绘图和视觉更新
plugin.draw(serialNumber, key, type?, base64?)
更新按键的视觉外观。
参数:
serialNumber(字符串):设备序列号key(对象):事件中获取的按键信息对象type(字符串):'draw'(默认)或 'base64'base64(字符串):Base64 图片数据(当 type 为 'base64' 时)
// Update key based on key.style properties
plugin.draw(serialNumber, key, 'draw')
// Draw custom image
const base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...'
plugin.draw(serialNumber, key, 'base64', base64Image)按键状态管理
plugin.setMultiState(serialNumber, key, state, message?)
设置多状态按键的当前状态。
参数:
serialNumber(字符串):设备序列号key(对象):按键对象state(数字):目标状态索引message(字符串,可选):显示的消息内容
plugin.setMultiState(serialNumber, key, 2, 'State Changed')plugin.setSlider(serialNumber, key, value)
设置滑动条按键的数值。
参数:
serialNumber(字符串):设备序列号key(对象):按键对象value(数字):滑动条的数值
plugin.setSlider(serialNumber, key, 75)Device Configuration & Control
plugin.sendControlCommand(serialNumber, command)
Sends control commands to the device
参数:
serialNumber(字符串):设备序列号command(string): The command to send. One of the following:- "sys.sleep": Put the device to sleep
- "sys.wake": Wake up the device
- "haptic.click": Trigger a click vibration
plugin.sendControlCommand(serialNumber, 'sys.sleep') // Put the device to sleep
plugin.sendControlCommand(serialNumber, 'sys.wake') // Wake up the device
plugin.sendControlCommand(serialNumber, 'haptic.click') // Trigger a click vibrationplugin.setDeviceConfig(serialNumber, config)
配置设备设置。
参数:
serialNumber(字符串):设备序列号config(对象):配置对象
plugin.setDeviceConfig(serialNumber, {
sleepTime: 1000,
brightness: 100,
screenFlip: true,
vibrate: 'full',
autoSleep: true,
deviceName: 'My Flexbar',
cdcMode: true,
color: 'space black'
})消息与通知
plugin.showFlexbarSnackbarMessage(serialNumber, msg, level, icon?, timeout?, waitUser?)
在 Flexbar 设备上显示一条消息。
参数:
serialNumber(字符串):设备序列号msg(字符串):消息内容(最多 64 个字符)level(字符串):消息级别('info'、'warning'、'error'、'success')icon(字符串,可选):图标名称timeout(数字,可选):显示时长(毫秒,500-10000,默认 2000)waitUser(布尔值,可选):是否等待用户交互
plugin.showFlexbarSnackbarMessage(
serialNumber,
'Hello from plugin!',
'info',
'bell',
3000
)plugin.showSnackbarMessage(color, message, timeout?)
在 FlexDesigner 应用程序中显示一条消息。
plugin.showSnackbarMessage('success', 'Operation completed!', 3000)系统集成
plugin.electronAPI(api, ...args)
调用 Electron API 以实现系统集成。
支持的 API:
dialog.showOpenDialogdialog.showSaveDialogdialog.showMessageBoxdialog.showErrorBoxapp.getAppPathapp.getPathscreen.getCursorScreenPointscreen.getPrimaryDisplayscreen.getAllDisplays
// Show file dialog
const result = await plugin.electronAPI('dialog.showOpenDialog', {
properties: ['openFile'],
filters: [{ name: 'Images', extensions: ['png', 'jpg'] }]
})
// Get cursor position
const cursorPos = await plugin.electronAPI('screen.getCursorScreenPoint')文件操作
plugin.openFile(path)
从文件系统读取文件。
const content = await plugin.openFile('/path/to/file.txt')plugin.saveFile(path, data)
将数据保存到文件。
await plugin.saveFile('/path/to/output.txt', 'Hello World!')应用信息
plugin.getAppInfo()
获取 FlexDesigner 应用程序的信息。
const appInfo = await plugin.getAppInfo()
// Returns: { version: "v1.0.0", platform: "win32" }plugin.getOpenedWindows()
获取系统中已打开窗口的列表。
const windows = await plugin.getOpenedWindows()
windows.forEach(win => {
console.log(`Window: ${win.title} (${win.bounds.width}x${win.bounds.height})`)
})plugin.getDeviceStatus()
获取已连接设备的状态。
const devices = await plugin.getDeviceStatus()
devices.forEach(device => {
console.log(`Device: ${device.serialNumber}, Status: ${device.status}`)
})配置管理
plugin.getConfig()
获取插件配置。
const config = await plugin.getConfig()
console.log('Current config:', config)plugin.setConfig(config)
更新插件配置。
await plugin.setConfig({ theme: 'dark', autoUpdate: true })性能监控
plugin.sendChartData(chartDataArray)
发送性能指标数据,在 FlexDesigner 中进行展示。
参数:
chartDataArray(数组):图表数据对象数组
plugin.sendChartData([
{
label: 'CPU Usage',
value: 45.2,
unit: '%',
baseUnit: '%',
baseVal: 45.2,
maxLen: 2,
category: 'system',
key: 'cpu'
},
{
label: 'Memory',
value: 2.1,
unit: 'GB',
baseUnit: 'MB',
baseVal: 2048,
maxLen: 3,
category: 'system',
key: 'memory'
}
])快捷键管理
plugin.updateShortcuts(shortcuts)
注册或注销键盘快捷键。
参数:
shortcuts(数组):快捷键信息对象数组
plugin.updateShortcuts([
{
shortcut: 'CommandOrControl+F1',
action: 'register'
},
{
shortcut: 'CommandOrControl+F2',
action: 'unregister'
}
])动态按键管理
plugin.dynamickey 对象提供了高级的按键管理能力。
plugin.dynamickey.clear(serialNumber, key)
移除容器中的所有动态按键。
plugin.dynamickey.clear(serialNumber, key)plugin.dynamickey.add(serialNumber, key, index, backgroundType, backgroundData, width, userData)
添加一个新的动态按键。
参数:
index(数字):插入按键的位置backgroundType(字符串):'base64' 或 'draw'backgroundData(字符串):图片数据或按键对象width(数字):按键宽度(像素,60-1000)userData(对象):与按键关联的自定义数据
plugin.dynamickey.add(
serialNumber,
key,
0,
'base64',
'data:image/png;base64,iVBORw0...',
200,
{ name: 'Dynamic Key 1', action: 'custom' }
)plugin.dynamickey.remove(serialNumber, key, index)
移除指定索引位置的动态按键。
plugin.dynamickey.remove(serialNumber, key, 2)plugin.dynamickey.move(serialNumber, key, srcIndex, dstIndex)
将动态按键从一个位置移动到另一个位置。
plugin.dynamickey.move(serialNumber, key, 0, 3)plugin.dynamickey.setWidth(serialNumber, key, width)
更改动态按键容器的宽度。
plugin.dynamickey.setWidth(serialNumber, key, 800)plugin.dynamickey.draw(serialNumber, key, index, backgroundType, backgroundData, width)
更新指定动态按键的视觉外观。
plugin.dynamickey.draw(
serialNumber,
key,
1,
'base64',
'data:image/png;base64,iVBORw0...',
200
)plugin.dynamickey.update(serialNumber, key, index, userData)
更新动态按键的用户数据。
plugin.dynamickey.update(serialNumber, key, 0, { status: 'updated' })plugin.dynamickey.refresh(serialNumber, key)
刷新动态按键的显示(建议在宽度变更后调用)。
plugin.dynamickey.refresh(serialNumber, key)事件处理
插件事件
'plugin.alive'
当按键加载完成并可交互时触发。
plugin.on('plugin.alive', (payload) => {
const { serialNumber, keys } = payload
keys.forEach(key => {
console.log(`Key loaded: ${key.cid} at position ${key.uid}`)
// Initialize key based on its type
if (key.cid === 'com.example.counter') {
key.style.showTitle = true
key.title = '0'
plugin.draw(serialNumber, key, 'draw')
}
})
})'plugin.dead'
Triggered when plugin keys are destroyed
plugin.on('plugin.dead', (payload) => {
const { serialNumber, keys } = payload
keys.forEach(key => {
console.log(`Key destoried: ${key.cid}`)
})
})'plugin.data'
当用户与按键交互时触发。
plugin.on('plugin.data', (payload) => {
const { serialNumber, data } = payload
const key = data.key
if (key.cid === 'com.example.button') {
console.log('Button pressed!')
return { status: 'success', message: 'Button handled' }
}
if (key.cid === 'com.example.slider') {
console.log(`Slider value: ${data.value}`)
}
})'plugin.config.updated'
当插件配置发生变化时触发。
plugin.on('plugin.config.updated', (payload) => {
console.log('Configuration updated:', payload.config)
})系统事件
'system.shortcut'
当注册的快捷键被按下时触发。
plugin.on('system.shortcut', (payload) => {
console.log(`Shortcut pressed: ${payload.shortcut}`)
})'system.actwin'
当活动窗口发生变化时触发。
plugin.on('system.actwin', (payload) => {
const { oldWin, newWin } = payload
console.log(`Window changed: ${oldWin.title} -> ${newWin.title}`)
})设备事件
'device.status'
当设备连接状态发生变化时触发。
plugin.on('device.status', (devices) => {
devices.forEach(device => {
if (device.status === 'connected') {
console.log(`Device connected: ${device.serialNumber}`)
// Configure the newly connected device
plugin.setDeviceConfig(device.serialNumber, {
brightness: 80,
deviceName: 'My Plugin Device'
})
}
})
})UI 事件
'ui.message'
当收到来自插件 UI 的消息时触发。
plugin.on('ui.message', async (payload) => {
console.log('Message from UI:', payload)
if (payload.action === 'test') {
// Perform test operations
await testAPIs()
return 'Test completed!'
}
return 'Message received!'
})'ui.log'
处理来自插件 UI 的日志消息(自动处理)。
工具与辅助功能
日志记录器
SDK 提供了一个日志记录器实例,用于调试和监控。
const { logger } = require("@eniactech/flexdesigner-sdk")
logger.info('Information message')
logger.warn('Warning message')
logger.error('Error message')
logger.debug('Debug message')路径工具
访问插件专属路径。
const { pluginPath, resourcesPath } = require("@eniactech/flexdesigner-sdk")
console.log('Plugin directory:', pluginPath)
console.log('Resources directory:', resourcesPath)完整示例
以下是一个展示多种 SDK 功能的综合示例:
const { plugin, logger, pluginPath } = require("@eniactech/flexdesigner-sdk")
const { createCanvas } = require('@napi-rs/canvas')
// Store key data
const keyData = {}
// Plugin lifecycle
plugin.start()
// Handle key loading
plugin.on('plugin.alive', (payload) => {
const { serialNumber, keys } = payload
keys.forEach(key => {
keyData[key.uid] = key
switch (key.cid) {
case 'com.example.counter':
// Initialize counter
keyData[key.uid].counter = 0
key.style.showTitle = true
key.title = 'Click Me!'
plugin.draw(serialNumber, key, 'draw')
break
case 'com.example.slider':
// Set initial slider value
plugin.setSlider(serialNumber, key, 50)
break
case 'com.example.dynamic':
// 设置 dynamic keys
setupDynamicKeys(serialNumber, key)
break
}
})
})
// Handle user interactions
plugin.on('plugin.data', (payload) => {
const { serialNumber, data } = payload
const key = data.key
switch (key.cid) {
case 'com.example.counter':
// Increment counter
keyData[key.uid].counter++
key.title = `${keyData[key.uid].counter}`
plugin.draw(serialNumber, key, 'draw')
break
case 'com.example.wheel':
// Handle wheel rotation
showWheelFeedback(serialNumber, data.delta)
break
}
})
// Handle device connections
plugin.on('device.status', (devices) => {
devices.forEach(device => {
if (device.status === 'connected') {
logger.info(`Device connected: ${device.serialNumber}`)
// Configure device
plugin.setDeviceConfig(device.serialNumber, {
brightness: 100,
deviceName: 'SDK Example Device',
autoSleep: true
})
}
})
})
// Helper function for dynamic keys
function setupDynamicKeys(serialNumber, key) {
plugin.dynamickey.clear(serialNumber, key)
// Add multiple dynamic keys
for (let i = 0; i < 3; i++) {
const canvas = createCanvas(200, 60)
const ctx = canvas.getContext('2d')
// Draw custom background
ctx.fillStyle = `hsl(${i * 120}, 70%, 50%)`
ctx.fillRect(0, 0, 200, 60)
ctx.fillStyle = 'white'
ctx.font = '20px Arial'
ctx.textAlign = 'center'
ctx.fillText(`Key ${i}`, 100, 35)
const imageData = canvas.toDataURL()
plugin.dynamickey.add(
serialNumber,
key,
i,
'base64',
imageData,
200,
{ id: i, name: `Dynamic Key ${i}` }
)
}
}
// 性能 monitoring
setInterval(() => {
const memUsage = process.memoryUsage()
plugin.sendChartData([
{
label: 'Memory Usage',
value: memUsage.heapUsed / 1024 / 1024,
unit: 'MB',
baseUnit: 'bytes',
baseVal: memUsage.heapUsed,
maxLen: 3,
category: 'system',
key: 'memory'
}
])
}, 5000)
// Register shortcuts
setTimeout(() => {
plugin.updateShortcuts([
{
shortcut: 'CommandOrControl+Shift+F1',
action: 'register'
}
])
}, 1000)
logger.info('Plugin example started successfully!')最佳实践
性能
- 在宽度变更后请调用
plugin.dynamickey.refresh() - 尽量批量处理多个动态按键操作
- 优化图片尺寸以提升性能
错误处理
- 始终将异步操作包裹在 try-catch 代码块中
- 在调用特定类型方法前请校验按键类型
- 优雅地处理设备断开连接的情况
用户体验
- 为用户交互提供视觉反馈
- 为通知选择合适的消息级别
- 保持 snackbar 消息简洁且信息明确
开发
- 调试时请使用日志记录器而非 console.log
- 请在多设备连接场景下进行测试
- 在应用配置变更前请先校验配置
故障排查
常见问题
- 插件无法启动:请确保已提供正确的命令行参数
- 按键未更新:请检查是否使用了正确的序列号
- 动态按键未显示:请在宽度变更后调用
refresh() - 事件未触发:请确认事件处理器已正确注册
调试
通过设置合适的日志级别启用调试日志:
logger.debug('Debug information')监控 WebSocket 连接,确保插件能够与 FlexDesigner 正常通信。
许可协议
本 SDK 依据 EniacTech 规定的条款提供。请参阅您的许可协议以了解使用条款和条件。
