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!'
})

核心概念

插件生命周期

  1. 初始化:插件启动并连接到 FlexDesigner

  2. 活跃事件:当按键加载并准备就绪时触发

  3. 数据事件:当用户与按键交互时触发

  4. 配置:插件可以读取/写入配置数据

按键类型

  • 标准按键:基本按钮功能

  • 多状态按键:在不同状态之间循环

  • 滑块按键:连续值调整

  • 动态按键:动态管理的按键集合

  • 滚轮按键:旋转编码器支持

API 参考

内置模块

为了简化插件开发过程,我们预集成了几个可以直接使用的模块:

  1. @napi-rs/canvas

  2. express

  3. axios

如果您需要集成其他模块,请告诉我们

核心插件 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

    • “hapic.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, 'hapic.click') // Trigger a click vibration

plugin.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.showOpenDialog

  • dialog.showSaveDialog

  • dialog.showMessageBox

  • dialog.showErrorBox

  • app.getAppPath

  • app.getPath

  • screen.getCursorScreenPoint

  • screen.getPrimaryDisplay

  • screen.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':
                // Setup 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}` }
        )
    }
}

// Performance 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

  • 请在多设备连接场景下进行测试

  • 在应用配置变更前请先校验配置

故障排查

常见问题

  1. 插件无法启动:请确保已提供正确的命令行参数

  2. 按键未更新:请检查是否使用了正确的序列号

  3. 动态按键未显示:请在宽度变更后调用 refresh()

  4. 事件未触发:请确认事件处理器已正确注册

调试

通过设置合适的日志级别启用调试日志:

logger.debug('Debug information')

监控 WebSocket 连接,确保插件能够与 FlexDesigner 正常通信。

许可协议

本 SDK 依据 EniacTech 规定的条款提供。请参阅您的许可协议以了解使用条款和条件。