news 2026/6/17 14:12:51

ZigBee ZCL集群开发实战:颜色控制、镇流器与温控器实现详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ZigBee ZCL集群开发实战:颜色控制、镇流器与温控器实现详解

1. ZigBee ZCL集群开发:从协议栈到产品落地的实战拆解

如果你正在开发基于ZigBee 3.0的智能设备,无论是智能灯泡、调光镇流器还是恒温器,那么与ZigBee Cluster Library(ZCL)打交道是绕不开的一步。我接触过不少开发者,他们拿到像NXP JN-UG-3115这样的官方用户指南时,往往会被里面大量的枚举、宏定义和结构体搞得晕头转向。文档确实提供了标准定义,但如何把这些冰冷的代码片段,变成你设备里真正能跑起来的功能,中间隔着一条名为“工程实践”的鸿沟。

这份指南的核心价值,在于将ZCL规范中关于颜色控制(Colour Control)、镇流器配置(Ballast Configuration)和温控器(Thermostat)这三个关键集群的官方描述,转化为可落地、可调试的实战经验。ZCL的本质是一套“功能字典”,它定义了设备之间聊什么、怎么聊。比如,一个智能灯泡(客户端)告诉另一个智能灯泡(服务器):“把颜色调到饱和度50%、色相180度”,它们使用的就是颜色控制集群里定义好的“语言”。而你的工作,就是为你开发的设备配备好这套“语言能力”,并确保它说得准确、高效。

在接下来的内容里,我不会简单复述手册里的枚举值列表,而是会结合我调试这些集群时踩过的坑、总结的技巧,带你深入三个层面:第一,理解每个集群的设计哲学与核心应用场景;第二,掌握通过宏定义进行功能裁剪和配置的具体操作,这是节省代码空间和确保兼容性的关键;第三,剖析关键属性的交互逻辑与实现细节,比如温控器的“死区”(Dead Band)如何影响控制逻辑,镇流器的状态位如何反映真实故障。无论你是嵌入式软件工程师、物联网产品经理,还是对智能家居协议感兴趣的技术爱好者,这篇指南都将帮你把ZCL文档读“薄”,把开发思路理“清”。

2. 核心集群功能解析与设计选型考量

在动手写代码之前,我们必须搞清楚要实现的到底是什么功能,以及ZCL是如何通过集群来抽象这些功能的。选型错误会导致后期兼容性差、开发返工,甚至产品无法通过ZigBee联盟的认证。

2.1 颜色控制集群:超越简单的开关与调光

颜色控制集群(Cluster ID: 0x0300)是智能照明领域的核心。它远不止是让灯变色那么简单,而是一套完整的色彩管理系统。根据输入文档中的枚举列表,其属性集可以划分为几个关键维度:

  1. 基础色彩模型:这是核心。Current Hue(当前色相)和Current Saturation(当前饱和度)构成了最直观的HS色彩模型,适合通过手机App的色环来选取颜色。Current XCurrent Y则对应CIE 1931 xy色度坐标,这是一种与设备无关的色彩表示法,能确保不同品牌、不同型号的灯显示出尽可能一致的颜色,是实现跨品牌互联互通的基础。
  2. 色温控制Colour Temperature Mired(色温,单位:微倒度)是另一个重要维度。Mired(Micro Reciprocal Degree)是色温开尔文(K)的倒数乘以10^6,即Mired = 1,000,000 / 色温(K)。使用Mired的好处在于,它与人眼对色温变化的感知更接近线性。例如,从2700K(370 Mired)调到3000K(333 Mired),变化了37 Mired;从5000K(200 Mired)调到6500K(154 Mired),变化了46 Mired。虽然绝对温差后者更大,但前者的Mired变化值更接近,感知上的变化幅度也更相似。
  3. 高级功能Enhanced Current Hue(增强色相)将色相值从0-254扩展到0-65535,实现了更平滑的色彩过渡。Colour Loop(色彩循环)相关属性则用于实现预置的动态灯光效果。

实操心得:色彩能力定义(Colour Capabilities)是灵魂文档中提到的CLD_COLOURCONTROL_COLOUR_CAPABILITIES宏定义,是你必须仔细权衡的地方。它不是一个简单的开关,而是你设备的功能宣言。例如,一个只支持调色温的吸顶灯,你应该只定义COLOUR_CAPABILITY_COLOUR_TEMPERATURE_SUPPORTED。如果你错误地启用了COLOUR_CAPABILITY_HUE_SATURATION_SUPPORTED,手机App就会展示出色环控件,但你的灯硬件根本无法响应,导致用户体验割裂。正确的做法是严格根据硬件驱动IC(如TI的TLC59731,NXP的JN5169内置PWM)支持的颜色通道(RGB, RGBW, RGBWW)来决定启用哪些能力。

2.2 镇流器配置集群:照明系统的“管家”

镇流器配置集群(Cluster ID: 0x0301)关注的是驱动光源的电子装置本身,它管理的是“如何驱动”,而非“驱动成什么颜色”。这在商用照明、路灯等场景中至关重要。其属性分为四大集:

  1. 镇流器信息Physical Min/Max Level定义了硬件所能达到的绝对亮度极限。比如一款LED驱动芯片,其PWM调光范围可能在5%-100%,那么物理最小值就是5%(对应值0x0A,因为0x01代表0.1%)。
  2. 镇流器设置Min/Max Level是用户或系统管理员可配置的运行范围,必须在物理范围之内。PowerOnLevelPowerOnFadeTime决定了上电时的行为——是瞬间全亮,还是缓缓亮起到某个记忆亮度?BallastFactorAdjustment则用于灯具老化补偿,可以逐年微调输出,维持初始的照度水平。
  3. 灯具信息与设置:这里记录了负载(灯泡/灯管)的详细信息,如LampType(灯具型号)、LampRatedHours(额定寿命)、LampBurnHours(已燃点时间)。LampAlarmModeLampBurnHoursTripPoint结合,可以实现基于时间的维护预警功能。

避坑指南:亮度值的映射与曲线文档提到亮度值(0x01-0xFE)对应0.1%-100%,但这只是线性映射。在实际的LED驱动中,人眼对亮度的感知是非线性的(近似对数曲线)。因此,你需要在固件中实现一个Gamma校正表,将ZCL的线性值转换为PWM的占空比。例如,ZCL值0x80(50%)经过Gamma校正后,对应的PWM占空比可能只有20%左右,这样人眼看起来才是均匀的50%亮度变化。忽略这一点,调光会显得前慢后快,非常不自然。

2.3 温控器集群:环境控制的“大脑”

温控器集群(Cluster ID: 0x0201)是HVAC(暖通空调)系统的指挥中心。它的设计体现了典型的控制逻辑:测量、设定、比较、执行。其核心在于处理多组设定点(Setpoint)和模式(Mode)。

  1. 双设定点与占用状态:这是最精妙的设计之一。它区分了Occupied(有人)和Unoccupied(无人)两种场景下的加热/冷却设定点。例如,白天办公室有人时,制冷设定点为24°C;晚上无人时,可自动放宽到28°C以节能。Occupancy属性(通常来自人体传感器)用于自动切换。
  2. 死区与限制MinSetpointDeadBand(最小设定点死区)至关重要。它强制要求制冷设定点必须至少比加热设定点高这个值(例如1°C)。这是为了防止系统在临界温度附近频繁地在加热与制冷模式间切换,既耗能又损耗设备。Abs/Min/Max ... Limit等一系列属性则构成了多级限制:制造商绝对极限、用户可调范围、当前有效范围,确保了系统的安全与灵活。
  3. 控制序列与系统模式ControlSequenceOfOperation定义了设备的物理能力(如“仅制冷”、“制冷加热四管制”),而SystemMode定义了当前运行模式(如“自动”、“仅加热”)。App在显示模式选择时,必须根据前者来禁用不支持的模式选项。

经验之谈:温度值的处理与传感器选择温度属性(如LocalTemperature)以0.01°C为单位��使用16位有符号整数表示。这里有两个坑:第一,传感器精度。如果你用的MCU内置温度传感器精度只有±1°C,那么上报0.01°C的分辨率就毫无意义,且会带来不必要的网络流量。应选择匹配的传感器,如DS18B20(±0.5°C)或更高精度的型号。第二,无效值处理。0x8000代表无效读数。当传感器失效或初始化未完成时,必须上报此值,而不是一个可能误导系统的随机温度值。客户端(如网关)在收到此值时,应显示“传感器故障”而非一个具体的温度数字。

3. 工程实现:从宏定义到代码集成

理解了集群的功能,下一步就是将其融入你的固件工程。这个过程的核心是编译时配置,通过修改zcl_options.h等头文件来实现。

3.1 颜色控制集群的配置与实现细节

首先,在zcl_options.h中启用集群和服务器端(假设你的设备是受控的灯):

#define CLD_COLOUR_CONTROL // 启用颜色控制集群 #define COLOUR_CONTROL_SERVER // 本设备作为服务器 // #define COLOUR_CONTROL_CLIENT // 通常不需要,除非设备也控制其他灯

接下来,根据你的硬件能力,定义色彩能力。假设我们开发一款全彩LED灯带控制器(支持RGB调色和色温调节):

// 在项目特定的配置文件中,例如 app_zcl_options.h #define CLD_COLOURCONTROL_COLOUR_CAPABILITIES \ (COLOUR_CAPABILITY_HUE_SATURATION_SUPPORTED | \ COLOUR_CAPABILITY_ENHANCE_HUE_SUPPORTED | \ COLOUR_CAPABILITY_XY_SUPPORTED | \ COLOUR_CAPABILITY_COLOUR_TEMPERATURE_SUPPORTED) // 注意:我们未启用 COLOUR_CAPABILITY_COLOUR_LOOP_SUPPORTED,因为硬件不支持动态效果

这个宏定义会触发内部一系列子属性的自动启用,如u8CurrentHue,u16CurrentX,u16ColourTemperatureMired等。

然后,你需要处理属性写入命令。当手机App发送一个“Move to Hue and Saturation”命令时,ZCL栈会调用你注册的回调函数。你的关键任务是将ZCL的属性值(如Hue: 0-254)转换为硬件PWM的占空比。这里有一个色彩空间转换的典型过程:

  1. HS/HSV 转 RGB:如果收到的是色相/饱和度,需先将HSV转换到RGB。注意ZCL的饱和度(Saturation)是0-254,对应0%-100%,而标准的HSV饱和度是0-1。转换公式(简化)如下:
    C = V * S(其中V为亮度值,来自Level Control集群) ...
  2. RGB 转 PWM:将归一化的R、G、B值(0.0-1.0)映射到PWM寄存器值(如0-1023)。这里必须应用前面提到的Gamma校正。通常用一个256项的查找表(LUT)来实现,效率最高。
  3. 色温混合:如果同时支持RGB和色温(RGBW灯珠),当设定为纯白光模式时,需要关闭RGB通道,仅通过色温值控制冷白(CW)和暖白(WW)通道的PWM比例。这涉及到另一个从Mired到CW/WW混合比例的转换。

关键实现:属性报告配置为了让网关能实时获取灯的状态,必须配置属性报告。例如,配置CurrentHue属性在变化超过5(约7°)或每隔300秒无变化时上报:

tsZCL_AttributeReportingConfigurationRecord sReportingConfig; sReportingConfig.u16AttributeEnum = E_CLD_COLOURCONTROL_ATTR_CURRENT_HUE; sReportingConfig.u16TimeoutPeriod = 300; // 秒 sReportingConfig.u8Direction = E_ZCL_DR_SERVER_TO_CLIENT; sReportingConfig.reportConfiguration.u16MinInterval = 1; // 最小报告间隔 sReportingConfig.reportConfiguration.u16MaxInterval = 300; sReportingConfig.reportConfiguration.u16ReportableChange = 5; // 可报告变化值 eZCL_ConfigureAttributeReporting(&sReportingConfig);

不配置报告,网关只能通过“读”命令轮询,效率低下且增加网络负载。

3.2 镇流器配置集群的集成与参数管理

对于镇流器设备,启用集群:

#define CLD_BALLASTCONFIGURATION #define BALLAST_CONFIGURATION_SERVER

你需要根据硬件规格,在zcl_options.h或初始化代码中设置关键参数:

// 硬件物理限制 #define CLD_BALLASTCONFIGURATION_ATTR_PHYSICAL_MIN_LEVEL 0x0A // 1.0% #define CLD_BALLASTCONFIGURATION_ATTR_PHYSICAL_MAX_LEVEL 0xFE // 100% // 默认运行限制 #define CLD_BALLASTCONFIGURATION_ATTR_MIN_LEVEL 0x0A // 1.0% #define CLD_BALLASTCONFIGURATION_ATTR_MAX_LEVEL 0xFE // 100% #define CLD_BALLASTCONFIGURATION_ATTR_POWER_ON_LEVEL 0xFF // 恢复上次亮度 #define CLD_BALLASTCONFIGURATION_ATTR_POWER_ON_FADE_TIME 50 // 5.0秒淡入

在应用初始化函数中,你需要调用文档中提到的eCLD_BallastConfigurationCreateBallastConfiguration来创建集群实例,并传入一个tsCLD_BallastConfiguration结构体指针用于存储属性。这里有一个极易出错的地方:结构体中的sLampTypeau8LampType[16]是配对使用的。你必须确保字符串数据同时正确地设置在这两个地方,否则读取属性时可能会得到乱码或错误长度。

灯具寿命预警功能的实现逻辑

  1. 在设备上电初始化时,从非易失存储器(如Flash)中读取已保存的u32LampBurnHours
  2. 在主循环中,每1秒(或一个计时周期)检查一次调光输出是否大于0。如果大于0,则将u32LampBurnHours加1(秒),但通常我们按小时累加更实际,可以每3600秒加1。
  3. u32LampBurnHours达到u32LampBurnHoursTripPoint(且对应报警位在u8LampAlarmMode中使能),则设置u8BallastStatus中的相应故障位,并主动发送一个“告警”报告到网关,而不是等网关来轮询。

3.3 温控器集群的逻辑与状态机实现

温控器的逻辑相对复杂,因为它是一个闭环控制系统。启用集群:

#define CLD_THERMOSTAT #define THERMOSTAT_SERVER

核心控制逻辑的实现步骤

  1. 确定当前设定点:根据u8OccupancyeSystemMode,决定使用哪组设定点。

    int16_t i16HeatingSetpoint, i16CoolingSetpoint; if (u8Occupancy & 0x01) { // Bit 0 = 1, 有人 i16HeatingSetpoint = sThermostatAttrs.i16OccupiedHeatingSetpoint; i16CoolingSetpoint = sThermostatAttrs.i16OccupiedCoolingSetpoint; } else { i16HeatingSetpoint = sThermostatAttrs.i16UnoccupiedHeatingSetpoint; i16CoolingSetpoint = sThermostatAttrs.i16UnoccupiedCoolingSetpoint; } // 还需要检查 Unoccupied 设定点是否被配置,否则可能回退到 Occupied 设定点
  2. 获取当前温度:从i16LocalTemperature读取,注意处理无效值0x8000。

  3. 决策与输出:根据eControlSequenceOfOperation和设备能力,执行决策。

    switch(sThermostatAttrs.eSystemMode) { case E_ZCL_THERMOSTAT_SYSTEM_MODE_HEAT: if (i16CurrentTemp < i16HeatingSetpoint - i8Deadband/2) { // 开启加热继电器或提高PI输出 sThermostatAttrs.u8PIHeatingDemand = 100; } else if (i16CurrentTemp >= i16HeatingSetpoint) { // 关闭加热 sThermostatAttrs.u8PIHeatingDemand = 0; } sThermostatAttrs.u8PICoolingDemand = 0; break; case E_ZCL_THERMOSTAT_SYSTEM_MODE_AUTO: // 自动模式需同时判断加热和制冷需求,并遵守死区 if (i16CurrentTemp < i16HeatingSetpoint - i8Deadband/2) { sThermostatAttrs.u8PIHeatingDemand = 100; sThermostatAttrs.u8PICoolingDemand = 0; } else if (i16CurrentTemp > i16CoolingSetpoint + i8Deadband/2) { sThermostatAttrs.u8PIHeatingDemand = 0; sThermostatAttrs.u8PICoolingDemand = 100; } else { // 处于舒适区,都关闭 sThermostatAttrs.u8PIHeatingDemand = 0; sThermostatAttrs.u8PICoolingDemand = 0; } break; // ... 其他模式 }
  4. PI控制循环u8PIHeatingDemandu8PICoolingDemand通常是比例-积分控制器的输出。你可以实现一个简单的软件PI控制器,根据温度与设定点的偏差,动态计算这个需求百分比,用于控制变频压缩机或调节阀门的开度,实现更平稳的温度控制,避免继电器频繁通断。

4. 调试、认证与进阶优化

功能实现后,真正的挑战在于让它稳定、可靠地工作,并通过ZigBee联盟的认证。

4.1 常见问题排查与调试技巧

  1. 设备无法加入网络或集群命令无响应

    • 检查端点(Endpoint)和集群ID:确保你的设备在正确的端点(通常是1)上实例化了这些集群,并且声明的集群ID(0x0300, 0x0301, 0x0201)与文档一致。使用抓包工具(如Nordic的nRF Sniffer, Silicon Labs的Packet Trace)查看入网和属性读写报文。
    • 验证属性权限:确认关键属性(如CurrentHue,LocalTemperature)的权限被正确设置为READ | REPORTABLE,可写属性(如OccupiedHeatingSetpoint)设置为READ | WRITE | REPORTABLE。权限错误会导致网关无法写入或订阅报告。
  2. 属性报告不工作或延迟高

    • 检查最大报告间隔u16MaxInterval设置过大(如65535秒)会导致报告极其缓慢。对于需要快速响应的属性(如开关状态),应设置为较小的值(如1-30秒)。
    • 检查“可报告变化”值u16ReportableChange对于温度是0.01°C单位。如果你设置为100(即1°C),那么温度变化0.5°C是不会触发报告的。对于亮度(0-254),通常设置为1或2即可。
    • 确认网络状态:在电池设备上,为了省电,可能设置了较长的父节点轮询间隔。这会导致子设备上报的数据被缓存,直到父节点来询问时才发送。调整Poll Interval可以改善实时性。
  3. 色彩显示不一致或色温不准

    • 校准!校准!再校准!这是硬件相关性问题。CIE xy坐标和Mired色温值必须与你的LED灯珠的实际光谱进行映射。你需要使用光谱仪或高精度色度计,测量你的灯珠在满功率下R、G、B、W各通道的xy坐标和亮度。然后生成一个颜色混合矩阵和查找表,固化在Flash中。没有校准,颜色控制根本谈不上准确。
    • 检查PWM分辨率与频率:PWM分辨率太低(如8位)会导致色彩阶跃感强。建议使用16位PWM。PWM频率太低(如100Hz)可能导致人眼可察觉的闪烁,尤其在摄像头下;频率太高(如>20kHz)可能超出驱动芯片的响应能力。通常1-5kHz是常见选择。

4.2 ZigBee 3.0 兼容性与认证准备

ZigBee 3.0 统一了之前的ZigBee HA, ZLL等不同应用规范,要求更严格。

  1. 设备描述符(Device Description):你必须正确声明你的设备类型。例如,一个支持全彩和色温的灯,应使用“Extended Color Light”的设备ID(如0x010D),而不是普通的“Color Light”或“Dimmable Light”。设备ID错误,会导致网关无法正确识别并提供对应的控制界面。
  2. 必选与可选集群:对于“Extended Color Light”,颜色控制集群是必选的(Mandatory),而镇流器配置集群通常是可选的(Optional)。但如果你声称支持,就必须完整实现其强制(Mandatory)属性。ZigBee联盟的测试套件(Zigbee Compliance Test Tool)会严格检查这一点。
  3. 集群版本(Cluster Revision):务必正确设置u16ClusterRevision属性。对于基于ZCL r6(ZigBee 3.0基础)的实现,应设置为1。使用错误的版本号可能导致与较新版本网关的兼容性问题。
  4. 安全与 commissioning:确保你的设备支持ZigBee 3.0的安装码(Install Code)或集中式密钥分发等安全入网方式。这是认证的必测项。

4.3 性能与资源优化建议

在资源受限的嵌入式设备上(如基于JN5169, EFR32MG的SoC),优化至关重要。

  1. 按需启用属性:只启用你产品真正需要的属性。例如,如果你的灯不支持色彩循环,就不要定义COLOUR_CAPABILITY_COLOUR_LOOP_SUPPORTED和相关的循环属性。这能显著节省RAM(用于属性存储)和Flash空间。
  2. 合理使用报告:不要为所有属性都配置主动报告。对于不常变化或非关键的属性(如LampManufacturer),可以禁用报告,仅支持“读”命令。
  3. 合并状态上报:对于镇流器状态(BallastStatus)、温控器报警等,可以设计一个逻辑,在多个相关状态变化时,合并到一次上报中,或使用“状态变化”位图属性,减少网络报文数量。
  4. 异步事件处理:颜色渐变、淡入淡出、温控器的PI计算等耗时操作,应放在低优先级任务或定时器中断中处理,避免阻塞ZigBee协议栈的主事件循环,导致网络通信卡顿甚至超时。

开发ZigBee设备是一个系统工程,ZCL集群是实现功能互通的基石。从理解集群规范,到通过宏定义进行功能裁剪,再到编写属性读写和命令处理的回调函数,最后进行严格的测试与校准,每一步都需要耐心和细致。希望这份结合了官方文档与实战经验的指南,能帮助你更顺畅地开发出稳定、互联互通且用户体验优秀的ZigBee智能设备。记住,成功的物联网产品,不仅是功能的堆砌,更是稳定性、互操作性和用户体验的完美结合。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/17 14:10:36

计算机Java毕设实战-基于 SpringBoot 的计算思维科普与人工智能学习系统设计 人工智能教育视角下线上学习平台的设计与实现【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/17 14:09:49

0617晨间日记

# 0617晨间日记 - 关键词 - 上午- 开发- 需求&#xff1a; juki能不能进行程式的对比- 难题&#xff1a;e48的格式无法进行解析&#xff0c;原因是他不是x01的xml文件&#xff0c;而是机器码- 既然不好解决- 可以不解决&#xff0c;将剩余的泛用机的零件列出来- 泛用机的用的零…

作者头像 李华
网站建设 2026/6/17 14:06:50

互联网大厂Java面试:Java EE与Spring Boot的技术问答

面试官与水货程序员燕双非的技术问答&#xff1a;Java EE、Spring Boot与微服务在互联网大厂的面试中&#xff0c;技术问题层出不穷。今天我们将跟随面试官与搞笑的候选人燕双非的对话&#xff0c;探讨Java EE、Spring Boot、微服务等技术点。第一轮提问 面试官&#xff1a;燕双…

作者头像 李华
网站建设 2026/6/17 14:05:09

3步实现无语言障碍浏览:鼠标悬停翻译工具完整指南

3步实现无语言障碍浏览&#xff1a;鼠标悬停翻译工具完整指南 【免费下载链接】MouseTooltipTranslator Mouseover Translate Any Language At Once - Chrome Extension: PDF Translator, EBOOK, EPUB, OCR, TTS, NETFLIX, YOUTUBE DUAL SUBTITLES, GOOGLE DOCS, AI, VIEWER, G…

作者头像 李华
网站建设 2026/6/17 14:02:23

Bootstrap Icons完整指南:免费获取2000+专业SVG图标的终极方案

Bootstrap Icons完整指南&#xff1a;免费获取2000专业SVG图标的终极方案 【免费下载链接】icons Official open source SVG icon library for Bootstrap. 项目地址: https://gitcode.com/gh_mirrors/ic/icons Bootstrap Icons是一个完全免费开源的SVG图标库&#xff0c…

作者头像 李华
网站建设 2026/6/17 13:59:10

3分钟终极指南:如何让Figma界面秒变全中文

3分钟终极指南&#xff1a;如何让Figma界面秒变全中文 【免费下载链接】figmaCN 中文 Figma 插件&#xff0c;设计师人工翻译校验 项目地址: https://gitcode.com/gh_mirrors/fi/figmaCN 还在为Figma的英文界面而烦恼吗&#xff1f;每次设计时都要在"Pen Tool"…

作者头像 李华