杰理 SDK 文档中心
首页
首页
  • 快速开始

    • 环境要求与权限配置
    • 集成SDK依赖
    • 运行示例应用
  • 核心架构与协议

    • RCSP协议与数据通道
    • 蓝牙连接与设备管理
    • TWS双耳功能
    • 基础功能接口与自定义命令
  • 设备功能控制

    • 设备音乐控制与ID3信息
    • 文件浏览与传输
    • FM收音与发射
    • 灯光控制
    • 闹钟与时间管理
    • 查找设备与防丢
    • ANC与噪声处理
    • 按键功能设置
    • 彩屏仓控制
    • AI翻译
  • 音效与音频处理

    • 均衡器音效调节
    • 录音与语音控制
    • Line-in、SPDIF与声卡功能
    • 音频编解码库
  • 扩展功能库

    • OTA固件升级
    • 数据加密与解密
    • 图片与动图格式转换
  • 示例应用 btsmart

    • 应用架构与界面导航
    • 设备功能适配与数据层
    • 设备配置JSON与资源文件
  • 参考与版本

    • 错误码参考
    • 版本历史与更新日志
    • 开发文档中心导航

灯光控制

灯光控制是 PiHome App 中通过蓝牙 RCSP 协议对耳机/音箱等设备进行灯光(RGB 彩灯、色温灯、场景灯、闪烁灯)控制的完整能力,涵盖 UI 交互层、HSL 色彩处理、数据模型与 RCSP 蓝牙命令链路。

Purpose and Scope

本页面介绍 App 端「灯光控制」功能的完整实现机制,包括:

  • 灯光控制的数据模型 LightControlInfo 及其 11 字节蓝牙协议封装
  • UI 层各 Fragment(LightControlFragment、LightColorTemperatureFragment、LightSceneFragment 等)的职责划分
  • HSL 色彩空间同步策略与设计意图(为何用 hue/saturation 替代 color 同步)
  • 通过 RCSPController 与设备交互的 getLightControlInfo / setLightControlInfo API 及回调机制
  • 收藏颜色(ColorCollect)的本地持久化与动画交互

设备端固件协议、SDK 底层 BLE 传输实现(com.jieli.bluetooth 模块)属于蓝牙 SDK 自身范畴,不在本页展开;需要了解 SDK 基础能力(连接、设备信息、RCSPController 生命周期)可参考对应 SDK 文档页。

Overview

灯光控制是音箱/耳机类蓝牙设备的常见增值功能:设备内置 RGB LED 或色温灯,App 通过 RCSP(Real-time Control and Streaming Protocol)指令读取/写入设备灯光参数。App 端能力分三层:

  1. 数据模型层:LightControlInfo 描述灯光的完整状态(开关、模式、颜色、闪烁、场景、HSL 分量),并提供 toByteArray() 将状态编码为 11 字节的协议包。
  2. 业务/传输层:RCSPController 单例负责与当前连接设备通信,提供 getLightControlInfo(...) 与 setLightControlInfo(...) 异步接口,结果通过 BTRcspEventCallback#onLightControlInfo 回调。
  3. UI 交互层:LightContainerFragment 作为容器承载三个子页面 —— 彩色灯(LightControlFragment)、色温灯(LightColorTemperatureFragment)、场景灯(LightSceneFragment),并配合 LightModeDialog、收藏色网格等交互组件。

设计上最关键的一点(见 LightControlFragment.java 的类注释):UI 层使用 hue 和 saturation 代替 color 进行设备同步,以减少 SeekBar 拖动时的抖动;点击收藏时判断 hue/saturation 是否变化再同步 ColorView,避免 ColorPickView 偏移;亮度(luminance)SeekBar 的拉动不直接同步到 ColorPickView。这些细节体现了对蓝牙指令频率与 UI 流畅性的权衡。

Architecture

flowchart TD
    subgraph sg_UI["UI 层 (com.jieli.btsmart.ui.light)"]
        Container["LightContainerFragment<br/>(容器/入口)"]
        ColorTemp["LightColorTemperatureFragment<br/>(色温灯)"]
        Control["LightControlFragment<br/>(彩色灯/HSL)"]
        Scene["LightSceneFragment<br/>(场景灯)"]
        ModeDialog["LightModeDialog<br/>(模式选择)"]
    end

    subgraph sg_Data["数据模型层"]
        Model["LightControlInfo<br/>(data.model.bluetooth)"]
        LightMode["LightMode<br/>(res + name)"]
        Collect["ColorCollect / ColorCollectList<br/>(收藏色)"]
    end

    subgraph sg_Adapter["Adapter 层"]
        ColorAdapter["LightColorCollectAdapter"]
        ModeAdapter["LightModeAdapter"]
        SceneAdapter["LightSceneAdapter"]
    end

    subgraph sg_SDK["RCSP SDK 层 (com.jieli.bluetooth)"]
        RCSP["RCSPController<br/>(单例)"]
        EventCb["BTRcspEventCallback<br/>onLightControlInfo"]
        DeviceInfo["DeviceInfo<br/>isLightEnable / getCurFunction"]
    end

    subgraph sg_Device["设备端"]
        Dev["蓝牙设备<br/>(耳机/音箱 LED)"]
    end

    Container --> Control
    Container --> ColorTemp
    Container --> Scene
    Control --> ModeDialog
    Control --> ColorAdapter
    Scene --> SceneAdapter
    ModeDialog --> ModeAdapter
    Control --> Model
    ColorAdapter --> Collect

    Control -->|"getLightControlInfo / setLightControlInfo"| RCSP
    ColorTemp --> RCSP
    Scene --> RCSP
    RCSP --> EventCb
    RCSP --> DeviceInfo
    EventCb -->|"回调结果"| Control
    RCSP -->|"RCSP 指令/11 字节载荷"| Dev

架构说明

  • 入口:LightContainerFragment 是灯光功能的总容器,根据当前设备能力(DeviceInfo#isLightEnable())决定是否展示灯光入口,并按 tab 切换彩色灯 / 色温灯 / 场景灯三个子 Fragment。
  • 状态同步:LightControlFragment 持有 RCSPController.getInstance() 单例(LightControlFragment.java),所有读写设备灯光的操作都经由该单例,保证与 App 其他蓝牙功能共享同一连接状态。
  • 数据模型:App 侧 LightControlInfo(com.jieli.btsmart.data.model.bluetooth)是 UI 与 SDK 之间的中间层,其 toByteArray() 决定最终写入蓝牙的字节布局;SDK 侧另有 com.jieli.bluetooth.bean.device.light.LightControlInfo 用于回调解析。
  • 回调驱动:读取结果是异步的,先注册 BTRcspEventCallback 监听,命令执行成功后数据在 onLightControlInfo 中返回,UI 据此刷新控件,避免轮询。

数据模型:LightControlInfo

App 侧模型 com.jieli.btsmart.data.model.bluetooth.LightControlInfo 完整描述了灯光状态,共 8 个整型字段:

字段类型含义
switchStateint灯光开关状态(bit0,0/1)
lightModeint灯光模式(bit1-2,与 switchState 共同压缩进 byte0)
colorintRGB 颜色值(Android Color 整型,写入时拆分为 R/G/B)
twinkleModeint闪烁模式
twinkleFreqint闪烁频率
sceneModeint场景模式
hueint色相(HSL,2 字节)
saturationint饱和度(HSL)
luminanceint亮度(HSL)

字段定义见 LightControlInfo.java。

注意:仓库中存在两个同名类 —— App 业务模型 com.jieli.btsmart.data.model.bluetooth.LightControlInfo(含 toByteArray() 编码逻辑)与 SDK 回调模型 com.jieli.bluetooth.bean.device.light.LightControlInfo(用于 BTRcspEventCallback#onLightControlInfo 解析)。UI 层 LightControlFragment 与 Demo 直接使用 SDK 版本,App 业务模型则负责 UI 状态 → 协议字节的转换。

11 字节协议封装

toByteArray() 将上述字段编码为固定 11 字节的载荷,布局如下:

字节索引内容来源字段
0switchState | (lightMode << 2)开关 + 模式 压缩
1-3R、G、BColor.red/green/blue(color)
4闪烁模式twinkleMode
5闪烁频率twinkleFreq
6场景模式sceneMode
7-8色相(2 字节大端)CHexConver.int2byte2(hue)
9饱和度saturation
10亮度luminance

实现见 LightControlInfo.java:

public byte[] toByteArray() {
    byte[] bytes = new byte[11];
    int byte0Value = switchState | (lightMode << 2);
    byte[] bytesHue = CHexConver.int2byte2(hue);
    bytes[0] = CHexConver.intToByte(byte0Value);
    bytes[1] = CHexConver.intToByte(Color.red(color));
    bytes[2] = CHexConver.intToByte(Color.green(color));
    bytes[3] = CHexConver.intToByte(Color.blue(color));
    bytes[4] = CHexConver.intToByte(twinkleMode);
    bytes[5] = CHexConver.intToByte(twinkleFreq);
    bytes[6] = CHexConver.intToByte(sceneMode);
    System.arraycopy(bytesHue, 0, bytes, 7, bytesHue.length);
    bytes[9] = CHexConver.intToByte(saturation);
    bytes[10] = CHexConver.intToByte(luminance);
    return bytes;
}

Source: LightControlInfo.java

设计意图:开关与模式共用 byte0 的位域(bit0 开关、bit1-2 模式),是为了压缩载荷、兼容设备端固件对单一状态字节的解析习惯;色相使用 2 字节(int2byte2)保证 0-360 的精度,而饱和度/亮度各 1 字节(0-255)即可满足 HSL 表示范围。Color.red/green/blue 与 CHexConver.intToByte 的组合确保 RGB 各分量以无符号字节传输。

模式与场景的轻量模型

LightMode(LightMode.java)是 UI 展示用的最小模型,仅包含两个字段:

public class LightMode {
    private int res;      // 模式图标资源 ID
    private String name;  // 模式名称
    // getter / setter ...
}

Source: LightMode.java

它由 LightModeAdapter 渲染到 LightModeDialog 的网格中,选中后把对应的模式索引写入 LightControlInfo.lightMode / sceneMode 并下发设备。收藏色则使用 ColorCollect / ColorCollectList 模型,由 LightColorCollectAdapter 以 6 列网格展示(GridLayoutManager(getContext(), 6),见 LightControlFragment.java)。

UI 交互层实现

页面结构与职责

灯光功能由 LightContainerFragment 聚合三个子页面:

  • LightControlFragment(彩色灯):核心页面。包含 ColorPicker2View 取色盘、三个 HSL SeekBar(sbHSLColdAndWarm 冷暖、sbHSLGrayAndColorful 灰彩、sbHSLDarkAndSun 明暗)、收藏色 RecyclerView(rvLightColorCollect)与收藏按钮(ibtnColorAdd)。控件绑定见 LightControlFragment.java。
  • LightColorTemperatureFragment(色温灯):面向仅支持色温调节的设备,提供冷暖色温滑杆。
  • LightSceneFragment(场景灯):提供预设场景选择(呼吸、律动等),由 LightSceneAdapter 渲染。

HSL 同步策略(设计意图)

LightControlFragment 的类注释(LightControlFragment.java)明确记录了三条关键决策:

  1. 用 hue/saturation 代替 color 同步:颜色拾取器返回 RGB,而设备指令需要 RGB;若直接回写 RGB,RGB→HSL→RGB 的往返换算会产生量化误差,导致 SeekBar 在相邻值之间抖动。因此 UI 维护独立 mValueColdAndWarm(色相)、mValueGrayAndColorful(饱和度)、mValueDarkAndLight(亮度)三个浮点状态,配合 ColorHSB、ColorHSL、ColorRGB、RGB2HSLUtil 工具类做单向换算。
  2. 收藏时按需同步 ColorView:点击收藏时先判断 hue/saturation 是否真的变化,再同步 ColorPicker2View,避免取色盘偏移。
  3. 亮度不同步到取色盘:拉动 luminance 的 SeekBar 只更新亮度通道,不反向驱动 ColorPickView,减少无谓重绘。

这些策略共同服务于一个目标:在有限的蓝牙指令带宽下,让 UI 操作与设备灯光表现保持稳定一致,避免视觉抖动与指令风暴。类中 isRealTimeRefresh = false(LightControlFragment.java)进一步说明取色盘回调默认只在手指抬起(end == true)时才真正同步状态,拖动过程仅本地预览。

收藏颜色的持久化与动画

收藏色列表以 KEY_COLLECT_COLORS_LIST 为键通过 PreferencesHelper 读写本地,首次进入使用 12 个空 ColorCollect 占位(LightControlFragment.java);收藏时触发 parabolaAnimation 抛物线动画(isParabolaAnimationShowing 防重入),将颜色"飞入"收藏格,提升交互反馈。

Core Flow:读取与设置灯光

sequenceDiagram
    participant UI as LightControlFragment
    participant RCSP as RCSPController
    participant SDK as SDK 协议栈
    participant DEV as 蓝牙设备

    Note over UI,DEV: 读取灯光状态
    UI->>RCSP: addBTRcspEventCallback(回调)
    UI->>RCSP: getLightControlInfo(device, cb)
    RCSP->>SDK: 发送 RCSP 查询指令
    SDK->>DEV: BLE 写特征
    DEV-->>SDK: BLE 通知(灯光信息载荷)
    SDK-->>RCSP: 解析为 LightControlInfo
    RCSP-->>UI: onLightControlInfo(device, info)
    UI->>UI: 刷新取色盘 / SeekBar / 场景视图

    Note over UI,DEV: 设置灯光状态
    UI->>UI: 构造 LightControlInfo(HSL→RGB)
    UI->>RCSP: setLightControlInfo(device, info, cb)
    RCSP->>SDK: 编码 11 字节载荷并发送
    SDK->>DEV: BLE 写特征
    DEV-->>RCSP: 应答结果
    RCSP-->>UI: onSuccess / onError

关键点:

  • 读取采用「先注册回调、再发指令」的模式:getLightControlInfo 的 OnRcspActionCallback 只表示指令已发送/送达,真正的数据在 BTRcspEventCallback#onLightControlInfo 异步返回(Demo 中注释明确「结果将会在 BTRcspEventCallback#onLightControlInfo 回调」,见 LightControlDemo.java)。
  • 设置则单次回调即可:setLightControlInfo 的 onSuccess/onError 直接反映设备应答结果。
  • 两个操作都要求先有连接中的设备(controller.getUsingDevice()),未连接时回调 BaseError。

Usage Examples

以下示例均摘自仓库源码,演示灯光控制能力的标准调用方式。

判断设备是否支持灯光 / 是否处于灯光模式

public boolean isSupportLightMode() {
    //获取RCSPController对象
    RCSPController controller = RCSPController.getInstance();
    //获取当前操作设备
    BluetoothDevice usingDevice = controller.getUsingDevice();
    if (null == usingDevice) return false;
    DeviceInfo deviceInfo = controller.getDeviceInfo(usingDevice);
    if (null == deviceInfo) return false; //设备未初始化
    return deviceInfo.isLightEnable();
}

public boolean isDeviceInLightMode() {
    //获取RCSPController对象
    RCSPController controller = RCSPController.getInstance();
    //获取当前操作设备
    BluetoothDevice usingDevice = controller.getUsingDevice();
    if (null == usingDevice) return false;
    DeviceInfo deviceInfo = controller.getDeviceInfo(usingDevice);
    if (null == deviceInfo) return false; //设备未初始化
    return deviceInfo.getCurFunction() == AttrAndFunCode.SYS_INFO_FUNCTION_LIGHT;
}

Source: LightControlDemo.java

设计意图:进入灯光页面之前先做两级校验 —— isLightEnable() 判断设备硬件是否带灯,getCurFunction() 判断设备当前是否已切换进灯光功能(部分设备功能需先切换)。这避免了向不支持灯光或未就绪的设备下发无效指令。

读取灯光控制信息

public void getLightControlInfo() {
    //获取RCSPController对象
    RCSPController controller = RCSPController.getInstance();
    //添加蓝牙RCSP事件监听器
    controller.addBTRcspEventCallback(new BTRcspEventCallback() {
        @Override
        public void onLightControlInfo(BluetoothDevice device, LightControlInfo lightControlInfo) {
            //此处将会回调灯光控制信息
        }
    });
    //执行获取灯光控制信息功能并等待结果回调
    controller.getLightControlInfo(controller.getUsingDevice(), new OnRcspActionCallback<Boolean>() {
        @Override
        public void onSuccess(BluetoothDevice device, Boolean message) {
            //成功回调
            //结果将会在BTRcspEventCallback#onLightControlInfo回调
        }

        @Override
        public void onError(BluetoothDevice device, BaseError error) {
            //失败回调
            //error - 错误信息
        }
    });
}

Source: LightControlDemo.java

设置灯光控制信息

public void setLightControlInfo(LightControlInfo lightControlInfo) {
    //获取RCSPController对象
    RCSPController controller = RCSPController.getInstance();
    //执行设置灯光控制功能并等待结果回调
    controller.setLightControlInfo(controller.getUsingDevice(), lightControlInfo, new OnRcspActionCallback<Boolean>() {
        @Override
        public void onSuccess(BluetoothDevice device, Boolean message) {
            //成功回调
        }

        @Override
        public void onError(BluetoothDevice device, BaseError error) {
            //失败回调
            //error - 错误信息
        }
    });
}

Source: LightControlDemo.java

编码 11 字节协议载荷

public byte[] toByteArray() {
    byte[] bytes = new byte[11];
    int byte0Value = switchState | (lightMode << 2);
    byte[] bytesHue = CHexConver.int2byte2(hue);
    bytes[0] = CHexConver.intToByte(byte0Value);
    bytes[1] = CHexConver.intToByte(Color.red(color));
    bytes[2] = CHexConver.intToByte(Color.green(color));
    bytes[3] = CHexConver.intToByte(Color.blue(color));
    bytes[4] = CHexConver.intToByte(twinkleMode);
    bytes[5] = CHexConver.intToByte(twinkleFreq);
    bytes[6] = CHexConver.intToByte(sceneMode);
    System.arraycopy(bytesHue, 0, bytes, 7, bytesHue.length);
    bytes[9] = CHexConver.intToByte(saturation);
    bytes[10] = CHexConver.intToByte(luminance);
    return bytes;
}

Source: LightControlInfo.java

API Reference

RCSPController#getLightControlInfo(BluetoothDevice device, OnRcspActionCallback<Boolean> callback)

发起读取设备灯光控制信息的 RCSP 指令。

  • 参数:
    • device(BluetoothDevice):目标设备,通常传 controller.getUsingDevice()。
    • callback(OnRcspActionCallback<Boolean>):指令发送结果回调。
  • 回调:onSuccess 表示指令已发送成功,灯光数据随后在 BTRcspEventCallback#onLightControlInfo(BluetoothDevice, LightControlInfo) 中返回;onError(BluetoothDevice, BaseError) 携带错误信息(如设备未连接、不支持灯光)。
  • 注意:使用前需先 addBTRcspEventCallback 注册事件监听,否则拿不到数据回调。

RCSPController#setLightControlInfo(BluetoothDevice device, LightControlInfo lightControlInfo, OnRcspActionCallback<Boolean> callback)

将 LightControlInfo 编码为 11 字节载荷并下发设备。

  • 参数:
    • device:目标设备。
    • lightControlInfo:灯光状态对象(SDK 版本 com.jieli.bluetooth.bean.device.light.LightControlInfo)。
    • callback:设置结果回调,onSuccess/onError 直接反映设备应答。
  • 返回:异步,无直接返回值。

BTRcspEventCallback#onLightControlInfo(BluetoothDevice device, LightControlInfo lightControlInfo)

设备灯光信息事件回调,读取操作的数据出口。

  • 参数:device 来源设备;lightControlInfo 设备上报的灯光状态(含开关、模式、颜色、闪烁、场景、HSL 等字段)。
  • 触发时机:getLightControlInfo 成功后的数据回包、设备主动上报灯光变化时。

DeviceInfo#isLightEnable() / DeviceInfo#getCurFunction()

能力探测接口:isLightEnable() 返回设备是否支持灯光功能;getCurFunction() 返回当前功能码,与 AttrAndFunCode.SYS_INFO_FUNCTION_LIGHT 比较可判断设备是否已处于灯光模式。

Failure Modes、边界情况与并发

设备未连接 / 未初始化

isSupportLightMode() 与 isDeviceInLightMode() 都显式判空:getUsingDevice() 返回 null、getDeviceInfo() 返回 null 时直接返回 false(LightControlDemo.java)。若绕过探测直接调用 getLightControlInfo/setLightControlInfo,SDK 会通过 OnRcspActionCallback#onError 返回 BaseError。UI 层应在收到错误时提示用户并禁用灯光入口。

设备不支持灯光功能

对 isLightEnable() == false 的设备,App 不应展示灯光页面;若设备硬件无灯但固件仍响应设置指令,指令会被设备静默忽略,App 侧表现为「设置成功但无视觉效果」—— 因此功能入口必须以能力探测结果为准,而不是以指令应答为准。

HSL↔RGB 换算误差

color(RGB)与 hue/saturation/luminance(HSL)并存于模型中,往返换算存在量化误差(尤其 luminance 接近 100 时 HSL 信息丢失)。LightControlFragment 的注释明确说明:保留 HSL 是因为 luminance=100 时无法保留 h/s,且 color 换算 HSL 容易产生误差。这正是 UI 用 HSL 分量做状态同步、RGB 仅用于最终下发的原因。

异步回调与并发

  • 回调注册:addBTRcspEventCallback 注册的是全局事件回调,若页面销毁(onDestroy)后未移除监听,可能导致内存泄漏或对已销毁视图的刷新。业务代码应在 Fragment 生命周期中对称注册/移除。
  • 快速连续操作:取色盘拖动会高频触发颜色变化,isRealTimeRefresh = false 保证只有抬手(end == true)才真正执行状态同步与指令下发,从源头限制蓝牙指令频率;收藏动画用 isParabolaAnimationShowing 标志防重入,避免连续点击收藏按钮导致动画叠加错乱。
  • 单例共享:RCSPController 是全局单例,多个页面(彩灯/色温/场景)同时操作时,最后一次 setLightControlInfo 覆盖前一次,符合「灯光单一状态」的物理语义;但轮询读取与用户设置并发时,应避免用读取结果覆盖用户刚设置的值(可用本地状态优先策略)。

Performance 与操作性建议

  • 指令频率控制:蓝牙 BLE 写特征存在吞吐上限,拖动滑块时若每帧都下发会阻塞协议栈。建议沿用「抬手同步」策略,或在需要实时预览时对指令做节流(例如 100-200ms 合并一次)。
  • 字节编码成本:toByteArray() 每次调用新建 11 字节数组并做若干次整数转换,成本极低;但注意 int2byte2 返回 2 字节数组,System.arraycopy 拷贝无额外 GC 压力,适合高频调用。
  • 本地持久化:收藏色通过 PreferencesHelper 同步读写,量小(12 个 ColorCollect),无需引入数据库;写入时机建议在动画结束后落盘,避免频繁 IO。

Extension Points

  • 新增灯光模式/场景:扩展 LightMode 模型(res + name)并在 LightModeDialog / LightSceneFragment 对应的 adapter 数据源中追加条目,同时保证设备固件侧模式索引一致;模式索引写入 LightControlInfo.lightMode / sceneMode 后由 setLightControlInfo 下发。
  • 自定义色彩处理:ColorHSB、ColorHSL、ColorRGB、RGB2HSLUtil 等工具类集中了色彩空间换算逻辑,新的交互(如色环取色、渐变)可复用;若引入新的同步策略,建议保持「HSL 为 UI 状态、RGB 为协议载荷」的分层,避免打破防抖机制。
  • 设备能力差异化:DeviceInfo#isLightEnable() 与 getCurFunction() 是现成的能力探测钩子,可据此决定展示彩灯 / 色温 / 场景中的哪些 tab;新设备类型(如仅支持场景灯)只需调整 LightContainerFragment 的 tab 装配逻辑。

Related Links

  • LightControlInfo.java(App 业务模型与协议编码)
  • LightControlFragment.java(彩色灯 UI 与 HSL 同步)
  • LightColorTemperatureFragment.java(色温灯页面)
  • LightSceneFragment.java(场景灯页面)
  • LightContainerFragment.java(灯光功能容器)
  • LightModeDialog.java(模式选择弹窗)
  • LightControlDemo.java(灯光控制 API 示例)
  • LightMode.java(模式数据模型)

相关能力:RCSP 协议的其他设备控制功能(如 EQ 音效、ANC 降噪、闹钟等)分属各自 Wiki 页面;蓝牙连接、设备发现与 RCSPController 生命周期属于「蓝牙 SDK 基础能力」页面范畴。

Prev
FM收音与发射
Next
闹钟与时间管理