借助蓝牙 5 的网状网络功能,开发人员可以增强无线连接系统(如物联网设备)的通信范围和网络可用性。但是,网状网络的低功耗无线硬件设计与网状网络软件开发之间存在着复杂的层次,这可能会使开发人员迅速陷入混乱并危及项目进度。
随着支持蓝牙 5 的智能手机和其他移动平台的出现,时间成为一个关键因素,因为几乎所有行业领域和应用对蓝牙网状网络能力都有需求,而且预计需求会爆炸式增长,开发人员需要快速响应。作为回应,硅片和软件供应商正在推出简化和加速开发流程的解决方案。
本文将概述蓝牙网状网络的基本原理,然后使用 Silicon Labs 支持网状网络的蓝牙 5 模块系列中的特定设备逐步介绍开发流程。利用这种集成式蓝牙 5 解决方案,开发人员可以快速部署联网设备和应用,从而充分利用蓝牙网状网络。
本文最后介绍 Silicon Labs 蓝牙网状网络软件开发包,其中详细说明了使用样例网状网络应用代码演示的事件驱动模型。
蓝牙网状网络需求
蓝牙网状网络超越了传统蓝牙技术的点对点连接能力。通过相邻联网设备中继消息,蓝牙网状网络将低功耗设备的有效覆盖范围扩展到其发射器功率输出和接收器灵敏度所能支持的实际范围以外。最重要的是,智能手机和其他移动设备的普及使得大家对蓝牙应用非常熟悉,蓝牙网状网络藉由这一事实,为更复杂的网状网络连接应用提供自然的演进。
在网状网络支持下,使用蓝牙的开发人员现在能够轻松连接家庭自动化、楼宇管理和任意数量物联网应用涉及的大量设备。
蓝牙网状网络工作原理
蓝牙网状网络使用概念上很简单的网络节点交互模型(图 1)。专用节点类型可提供节点之间中继消息所需的附加功能,从而扩展通过代理节点与支持蓝牙的移动设备进行交互的网络的有效范围。
图 1:除基本边缘节点外,蓝牙网状网络还能使用特殊节点类型为其他节点传递消息(中继),充当低功耗节点的缓存(好友),或者将网络(代理)连接到支持蓝牙的移动设备。(图片来源:Silicon Labs)
其他专用节点类型则可应对降低功耗的要求,使用好友节点缓存消息,以供低功耗节点在长时间休眠状态之间定期轮询。尽管具有这种附加功能,蓝牙网状网络设备仍然可以利用通用属性配置文件 (GATT) 服务来与使用早期蓝牙版本的旧设备进行连接。因此,网状网络设备可以充分利用现有低功耗蓝牙 (BLE) 能力(例如信标),以生成区域特定消息并发送给智能手机,或者将自身标识为资产管理应用。
蓝牙网状网络还能解决日益增长的对楼宇自动化或其他物联网应用所需受保护网络的安全性的关注。与提供可选安全性以保护单个设备的 BLE 不同,蓝牙网状网络实施的安全性试图保护整个网状网络。
蓝牙网状网络实现安全的方法特别有意义。其安全方案将“关注点分离”概念引入到网状网络中,为每个设备、网络和整体应用使用单独的安全措施。与每个设备相关联的私有设备密钥 (DevKey) 为仅涉及该节点的配置和调配等操作提供安全性。每个设备都需要网络密钥 (NetKey),才能与网络或子网中的其他节点进行通信。最后,应用级交互(例如发送消息以开灯)则需要应用密钥 (AppKey)。其他安全措施可用于防范中间人或重放攻击等常见威胁。所有措施相互配合,蓝牙网状网络中的安全机制为更复杂的物联网应用所需的信任提供了关键基础。
然而,实现蓝牙网状网络连接应用给开发人员带来了很大困难。大多数使用网状网络的应用是建立在功耗受限的设备之上,依靠网状网络来扩展低功耗无线电子系统的有效覆盖范围。创建支持网状网络的合适低功耗硬件设备所涉及的挑战,甚至能让最有经验的硬件开发人员停滞不前。即使在完成其定制蓝牙设计之后,为满足国家认证要求,开发人员也可能面临巨大的成本压力和旷日持久的延迟。软件开发人员在寻找兼容的蓝牙网状网络堆栈并利用其来构建软件层以便能支持自己的应用时,也会发生延迟。然而,借助 Silicon Laboratories 的蓝牙硬件和软件,开发人员可以在低功耗设备中快速部署蓝牙网状网络功能,以满足自己的应用需求。
蓝牙模块
Silicon Labs 的蓝牙网状网络解决方案基于其低功耗蓝牙 BGM13P 硬件模块,该模块结合了无线处理器和全套蓝牙堆栈,以 12.9×15.0×2.2 mm 的封装提供经过认证的完整蓝牙系统。该模块的核心是 EFR32BG13 Blue Gecko 无线片上系统 (SoC),可提供核心功能。EFR32BG13 SoC 集成了 32 位 Arm® Cortex®-M4 内核、2.4 GHz 无线电子系统、512 KB 闪存、64 KB RAM 以及丰富的模拟和数字外设。除了片上硬件加密加速器之外,该 SoC 还通过安全管理单元支持不断增长的更高安全性需求;该安全管理单元为外设提供的细粒度访问控制与存储器保护单元为存储器提供的相同。
EFR32BG13 SoC 可作为定制蓝牙硬件设计的基础。使用 SoC 时,开发人员不仅要负责满足 SoC 支持电路等设计要求,还要对完成的设计进行必要的认证。该模块提供有全面认证的设计,其中的 EFR32BG13 带有所需的支持电路,包括数个振荡器源、两个晶体和端口驱动器。与此同时,该模块还提供了一系列省电特性,因此开发人员能够响应持续存在的低功耗设备需求。
该模块在活动模式下仅消耗 87 µA/MHz,在全 RAM 保持的深度休眠模式下仅消耗 1.4 μA。为了帮助最大限度地延长停留在低功耗深度休眠模式下的时间,工程师可以利用低能耗传感器接口和低能耗定时器等特性。使用低能耗传感器接口,工程师可以对模块的集成有限状态机和模拟外设进行编程,以在处理器保持深度休眠模式的同时采集和处理传感器信号。类似地,通过低能耗定时器,工程师可以输出简单波形并监控实时时钟/计数器,以便在指定时间内执行操作,而无需处理器参与。
当然,无线设备的功耗一般取决于无线电子系统的效率。本例中,该模块的 2.4 GHz 无线电子系统在接收模式下仅消耗 9.9 mA,在 0 dBm 输出功率的发射模式下仅消耗 8.5 mA。即便如此,该模块还提供了通过射频控制节省功耗的额外特性。开发人员可以对模块中的射频检测功能进行编程,以在检测到宽带射频能量时唤醒处理器。通过这种方法,开发人员可以在无活动期间使模块保持深度休眠而不会丧失通信。但是,如前所述,开发人员也可以将某个设备配置为蓝牙 5 低功耗节点,其能够简单地定期从深度休眠中唤醒以轮询好友节点,获取缓存的消息。
系统开发
针对其所有特性,该模块在实现方面几乎没有任何困难。开发人员可以简单地将该模块放入一个带有处理器的设计中,将其用作蓝牙网络协处理器(图 2A)。或者,开发人员可以将该模块用作完整的系统解决方案(图 2B)。在这种独立模式下,开发人员可以在模块的 EFR32BG13 处理器上运行应用代码,并使用 EFR32BG13 集成的模拟和数字外设在简单的物联网设计中进行信号采集。
图 2:设计人员可以将 BGM13P 模块用作主机 CPU 的蓝牙协处理器 (A),或者单独使用 (B),利用模块集成的 EFR32BG13 SoC 执行应用程序甚至采集传感器数据。(图片来源:Silicon Labs)
开发人员可以使用该模块的一个集成天线的版本 BGM13P22F512GA-V2,以进一步简化蓝牙设计。针对要应对更具挑战性射频环境的设计,开发人员可以采用 BGM13P22F512GE-V2,这是一个带有 U.FL 连接器的版本,可以连接蓝牙兼容的平贴片天线,例如 Taoglas 的 FXP74.07.0100A。
Silicon Labs 甚至通过 SLWSTK6101C 开发套件消除了该级别的硬件实现。SLWSTK6101C 设计用于配合其不同蓝牙设备的插件板使用,提供代表性的物联网设计,包含 Macronix 的 MX25R8035F 8 Mb 闪存、Sharp Microelectronics 的 LS013B7DH03 128 x 128 LCD 和 Silicon Labs 的 Si7021 温度与湿度传感器。在这种情况下,开发人员将包含 BGM13P 模块的 SLWRB4306A 无线电电路板插入 SLWSTK6101C 板。
除了作为可立即投产的设计之外,全套电路板还提供经过验证的参考设计,工程师可以使用它来检查与闪存、LCD 和传感器等设备接口的不同方法。
例如,8 Mb 闪存和 LCD 通过其 SPI 总线连接到模块,而 Si7021 传感器的 I2C 接口与开发板上的外部针座共享总线。Silicon Labs 演示了一种设计简单接口的方法,它使传感器在正常情况下保持禁用并与共享总线电气隔离。当模块的 PD15 输入变为高电平时,SENSOR_ENABLE 输出变为高电平,将传感器连接到 3.3 V VMCU 电源轨和 I2C 总线(图 3)。
图 3:除了提供硬件评估平台之外,Silicon Labs SLWSTK6101C 开发套件还可充当参考设计,展示与此处所示的 Silicon Labs Si7021 传感器等外部设备接口的方法。(图片来源:Silicon Labs)
共享 I2C 总线针座只是设计用来支持该平台开发的几个特性之一(图 4)。除了板载 J-Link 调试器之外,该板还提供了数据包追踪接口 (PTI),允许工程师详细分析数据包。PTI 建立在 EFR32BG13 SoC 内置的数据包和状态追踪单元之上,提供对系统发送和接收的所有数据包的非侵入式捕捉。为了分析蓝牙网状网络等复杂协议,该数据包追踪功能提供了一个对于优化和调整低级网络通信至关重要的工具。
图 4:Silicon Labs SLWSTK6101C 套件有多个接口用于数据包追踪、能量监测和低级 Arm 嵌入式追踪宏单元 (ETM) 追踪,为工程师深入分析设计操作和性能提供了丰富的工具集。(图片来源:Silicon Labs)
虽然网络专家需要 PTI 这样的功能来优化网络,但系统开发人员需要能帮助其发现可能导致功耗过大的应用低效问题的工具。对于此类应用级功耗优化,Silicon Labs Simplicity Studio 能量分析器可提供代码级功耗分析。
同数据包追踪工具一样,能量分析器也是对底层硬件加以利用。在这种情况下,电路板包括一个专用能量监测电路,其由电流传感器电阻、电流检测放大器和增益级组成,将输出传送到电路板的控制器,供开发主机系统访问(图 5)。并联增益级允许能量监测器以两个不同的分辨率级别测量 0.1 μA 至 95 mA 的电流:250 μA 以上使用 0.1 mA 分辨率;低于 250 μA 阈值使用 1 μA 分辨率。
图 5:内置于 BGM13P 蓝牙模块的专用能量监测电路和处理控制器可提供 0.1 μA 至 95 mA 的非侵入式电流测量。(图片来源:Silicon Labs)
当能量监测电路产生电流测量结果时,EFR32BG13 内置的低级追踪机制可以定期对处理器的程序计数器进行采样,并将结果通过设备的串行线输出引脚输出。通过将能量监测器的结果与此程序追踪输出相结合,能量分析器可以实时显示与设备上运行的代码相关的能耗(图 6)。
图 6:Simplicity Studio 能量分析器将能量监测器输出与程序追踪数据相结合,以实时显示与实际代码相关的电流消耗。(图片来源:Silicon Labs)
网状网络应用开发
硬件工程师可以使用开发套件来优化其硬件设计,而软件开发人员可以利用 Silicon Labs 的综合软件开发环境来快速创建网状网络应用。Silicon Labs 的蓝牙 5 网状网络堆栈随同 Simplicity Studio 提供,其用特定网状网络资源扩展了基本蓝牙堆栈。因此,开发人员可以轻松地从较传统的蓝牙协议(如信标或点对点通信)转移到全网状网络拓扑(图 7)。
图 7:Silicon Labs 蓝牙网状网络堆栈用网状网络层(绿色)扩展了早期蓝牙功能(蓝色),使得开发人员能够充分利用从信标到全网状网络配置的全部蓝牙特性。(图片来源:Silicon Labs)
Simplicity Studio 与基于 Silicon Labs BGM13P 的 SLWRB4306A 和 SLWSTK6101C 开发板一起使用,让开发人员能利用适当的软件开发套件 (SDK) 配置其环境。对于蓝牙开发,Studio 提供了 Silicon Labs 的蓝牙网状网络 SDK 以及预先构建的演示二进制文件和源代码。在此环境中,开发人员可以使用实现了完整蓝牙网状网络应用的样例代码。
这些样例应用程序与开发板和移动应用配合使用,旨在演示蓝牙网状网络的操作,让开发人员能全面了解典型网状网络的操作,包括调配、配置和应用相关的使用。为了部署样例应用程序,工程师针对一组开发板运行 Simplicity Studio,这些开发板分别配置为联网照明应用中的灯或开关。通过使用样例代码和硬件,工程师可以更好地了解典型网状网络应用从设备上电开始的各个操作阶段。
借助 Silicon Labs 的软件架构,蓝牙操作可以一系列事件展开,使用预定义的事件 ID 来表示事件的性质。在样例软件包中,main() 例程在上电或复位时运行,先调用一系列初始化例程,然后进入主循环,本例中主循环只包含两行代码(列表 1)。
副本int main(){#ifdef FEATURE_SPI_FLASH /* Put the SPI flash into Deep Power Down mode for those radio boards where it is available */ MX25_init(); MX25_DP(); /* We must disable SPI communication */ USART_Reset(USART1);#endif /* FEATURE_SPI_FLASH */ enter_DefaultMode_from_RESET();#if (EMBER_AF_BOARD_TYPE == BRD4304A) LNA_init();#endif gecko_init(&config);#ifdef FEATURE_PTI_SUPPORT APP_ConfigEnablePti();#endif // FEATURE_PTI_SUPPORT RETARGET_SerialInit(); /* initialize LEDs and buttons.Note: some radio boards share the same GPIO for button & LED.* Initialization is done in this order so that default configuration will be button for those * radio boards with shared pins.led_init() is called later as needed to (re)initialize the LEDs * */ led_init(); button_init(); LCD_init(); while (1) { struct gecko_cmd_packet *evt = gecko_wait_event(); handle_gecko_event(BGLIB_MSG_ID(evt->header), evt); }}
列表 1:Simplicity Studio 提供了一个综合开发环境,其中包括样例代码,例如此网状网络照明主例程,其演示了初始化和事件处理循环。(代码来源:Silicon Labs)
在主循环的第一行中,函数 gecko_wait_event() 在阻塞流程的同时等待事件出现,事件队列由较低级别填充。虽然开发人员往往会避免使用阻塞功能,但此方法在这种情况下特别有效,因为蓝牙堆栈在此阻断模式下会自动管理低功耗休眠状态。对于不能容许阻塞等待的特定应用要求,SDK 还提供了一个非阻塞函数,如果队列为空,则返回下一个事件或 NULL。但使用此函数时,开发人员需要自行处理低功耗休眠管理。
在主循环的第二行中,处理函数 handle_gecko_event() 根据其事件 ID 处理最新事件 (evt)(列表 2)。当设备上电时,堆栈发出系统引导事件 (gecko_evt_system_boot_id)。事件处理程序进而调用一系列初始化函数,包括 gecko_cmd_mesh_node_init(),其会初始化蓝牙网状网络堆栈。然后,处理程序调用其他函数来提供与该事件类型(由其相关事件 ID 表示)相关联的功能。
副本/** * Handling of stack events.Both Bluetooth LE and Bluetooth mesh events are handled here.*/static void handle_gecko_event(uint32_t evt_id, struct gecko_cmd_packet *evt){ struct gecko_bgapi_mesh_node_cmd_packet *node_evt; struct gecko_bgapi_mesh_generic_server_cmd_packet *server_evt; struct gecko_msg_mesh_node_provisioning_failed_evt_t *prov_fail_evt; if (NULL == evt) { return; } switch (evt_id) { case gecko_evt_system_boot_id: // check pushbutton state at startup.If either PB0 or PB1 is held down then do factory reset if (GPIO_PinInGet(BSP_GPIO_PB0_PORT, BSP_GPIO_PB0_PIN) == 0 || GPIO_PinInGet(BSP_GPIO_PB1_PORT, BSP_GPIO_PB1_PIN) == 0) { initiate_factory_reset(); } else { struct gecko_msg_system_get_bt_address_rsp_t *pAddr = gecko_cmd_system_get_bt_address(); set_device_name(&pAddr->address); // Initialize Mesh stack in Node operation mode, wait for initialized event gecko_cmd_mesh_node_init(); // re-initialize LEDs (needed for those radio board that share same GPIO for button/LED) led_init(); } break; ...case gecko_evt_mesh_node_initialized_id: printf(node initialized\r\n); struct gecko_msg_mesh_node_initialized_evt_t *pData = (struct gecko_msg_mesh_node_initialized_evt_t *)&(evt->data); if (pData->provisioned) { ...} else { printf(node is unprovisioned\r\n); LCD_write(unprovisioned, LCD_ROW_STATUS); printf(starting unprovisioned beaconing...\r\n); gecko_cmd_mesh_node_start_unprov_beaconing(0x3); // enable ADV and GATT provisioning bearer } break; case gecko_evt_mesh_node_provisioning_started_id: printf(Started provisioning\r\n); LCD_write(provisioning..., LCD_ROW_STATUS); // start timer for blinking LEDs to indicate which node is being provisioned gecko_cmd_hardware_set_soft_timer(32768 / 4, TIMER_ID_PROVISIONING, 0); break; case gecko_evt_mesh_node_provisioned_id: _my_index = 0; // index of primary element hardcoded to zero in this example lightbulb_state_init(); printf(node provisioned, got index=%x\r\n, _my_index); // stop LED blinking when provisioning complete gecko_cmd_hardware_set_soft_timer(0, TIMER_ID_PROVISIONING, 0); LED_set_state(LED_STATE_OFF); LCD_write(provisioned, LCD_ROW_STATUS); break; case gecko_evt_mesh_node_provisioning_failed_id: prov_fail_evt = (struct gecko_msg_mesh_node_provisioning_failed_evt_t *)&(evt->data); printf(provisioning failed, code %x\r\n, prov_fail_evt->result); LCD_write(prov failed, LCD_ROW_STATUS); /* start a one-shot timer that will trigger soft reset after small delay */ gecko_cmd_hardware_set_soft_timer(2 * 32768, TIMER_ID_RESTART, 1); break; ...}}
列表 2:开发人员可以检查 Silicon Labs 网状网络样例代码中的关键设计模式,例如调配事件处理,相关代码片段位于网状网络灯主程序中调用的 handle_gecko_event() 事件处理程序(参见列表 1)。(代码来源:Silicon Labs)
蓝牙网状网络中的关键事件系列之一与调配过程有关。设备上电并完成其初始化序列之后,便进入信标模式,向网络宣告其自身以供调配。当调配完毕(或失败)时,样例代码会使用开发套件 LCD 和 LED 来指示状态。通过检查事件处理程序针对每个调配状态的代码块,开发人员可以快速了解该调配序列和选项。
同样,软件工程师可以使用样例处理程序代码作为创建其应用级功能的指南。例如,蓝牙网状网络中的一个关键概念是使用发布-订阅模型将共享某些功能关系的节点关联起来(图 8)。
图 8:应用开发人员使用蓝牙的发布-订阅模型将设备组合成功能分组,例如由一个或多个开关控制的一组灯。(图片来源:Silicon Labs)
通过这种方法,数个智能灯泡可以订阅一个开关发布者。当最终用户激活该开关时,其将发布 ON/OFF 事件。该事件将通过网状网络级联到订阅的智能灯泡,其事件处理程序将采取适当的操作。Silicon Labs 样例代码演示了这一过程:首先是网状网络中的联网开关发布 ON/OFF 请求(列表 3),然后是联网灯的相应响应(列表 4)。
副本/** * This function publishes one on/off request to change the state of light(s) in the group.* Global variable switch_pos holds the latest desired light state, possible values are * switch_pos = 1 -> PB1 was pressed, turn lights on * switch_pos = 0 -> PB0 was pressed, turn lights off * * This application sends multiple requests for each button press to improve reliability.* Parameter retrans indicates whether this is the first request or a re-transmission.* The transaction ID is not incremented in case of a re-transmission.*/void send_onoff_request(int retrans){ uint16 resp; uint16 delay; struct mesh_generic_request req; req.kind = mesh_generic_request_on_off; req.on_off = switch_pos ?MESH_GENERIC_ON_OFF_STATE_ON : MESH_GENERIC_ON_OFF_STATE_OFF; // increment transaction ID for each request, unless it's a retransmission if (retrans == 0) { trid++; } /* delay for the request is calculated so that the last request will have a zero delay and each * of the previous request have delay that increases in 50 ms steps.For example, when using three * on/off requests per button press the delays are set as 100, 50, 0 ms */ delay = (request_count - 1) * 50; resp = gecko_cmd_mesh_generic_client_publish( MESH_GENERIC_ON_OFF_CLIENT_MODEL_ID, _my_index, trid, 0, // transition delay, 0, // flags mesh_generic_request_on_off, // type 1, // param len &req.on_off /// parameters data )->result; if (resp) { printf(gecko_cmd_mesh_generic_client_publish failed,code %x\r\n, resp); } else { printf(request sent, trid = %u, delay = %d\r\n, trid, delay); }}
列表 3:这个来自 Silicon Labs 网状网络开关样例应用程序的代码片段说明了如何使用蓝牙 5 发布过程 (gecko_cmd_mesh_generic_client_publish) 来请求订阅该事件流的灯的状态改变(开或关)。(代码来源:Silicon Labs)
副本static void onoff_request(uint16_t model_id, uint16_t element_index, uint16_t client_addr, uint16_t server_addr, uint16_t appkey_index, const struct mesh_generic_request *request, uint32_t transition_ms, uint16_t delay_ms, uint8_t request_flags){ printf(ON/OFF request: requested state=<%s>, transition=%u, delay=%u\r\n, request->on_off ?ON : OFF, transition_ms, delay_ms); if (lightbulb_state.onoff_current == request->on_off) { printf(Request for current state received; no op\n); } else { printf(Turning lightbulb <%s>\r\n, request->on_off ?ON : OFF); if (transition_ms == 0 && delay_ms == 0) { // Immediate change lightbulb_state.onoff_current = request->on_off; lightbulb_state.onoff_target = request->on_off; if (lightbulb_state.onoff_current == MESH_GENERIC_ON_OFF_STATE_OFF) { LED_set_state(LED_STATE_OFF); } else { LED_set_state(LED_STATE_ON); } } else { // Current state remains as is for now lightbulb_state.onoff_target = request->on_off; LED_set_state(LED_STATE_TRANS); // set LEDs to transition mode gecko_cmd_hardware_set_soft_timer(TIMER_MS_2_TIMERTICK(delay_ms + transition_ms), TIMER_ID_TRANSITION, 1); } lightbulb_state_store(); } if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) { onoff_response(element_index, client_addr, appkey_index); } else { onoff_update(element_index); }}
列表 4:Silicon Labs 网状网络灯样例包括用于特定应用级事件的例程,例如这个打开或关闭 LED 以响应开关所发布请求的函数(参见列表 3)。(代码来源:Silicon Labs)
除了用于节点开发的蓝牙堆栈和 SDK 之外,Silicon Labs 还提供了蓝牙网状网络的最终环节——连接移动设备。大多数移动设备支持蓝牙 4,虽然其无线电可以支持蓝牙 5 要求,但它们没有支持蓝牙 5 网状网络层的堆栈。Silicon Labs 为移动应用开发人员提供了额外的软件堆栈来,而该堆栈可提供网状网络功能,因此便能克服这一局限性(图 9)。
图 9:开发人员可以将 Silicon Labs 针对移动设备的网状网络堆栈添加到其移动应用中,使得蓝牙 4 移动设备可以在蓝牙 5 网状网络中使用。(图片来源:Silicon Labs)
总结
蓝牙 5 网状网络为各种各样已经利用智能手机和其他移动设备进行点对点通信的应用提供了一个自然的过渡。然而,蓝牙 5 网状网络的部署对硬件和软件设计提出了重大挑战,特别是在物联网等功耗受限的应用中。此外,硬件工程师需要满足最小基底面和低功耗的要求,软件工程师则需要构建使用最少资源来执行复杂通信协议的软件。BGM13P 模块、SLWSTK6101C 开发板以及节点和移动设备的蓝牙堆栈的结合,使工程师有一个综合性平台来快速开发使用蓝牙网状网络的应用。
欢迎光临 照明论坛-LED论坛-照明家族 (http://lightingfamily.net/) | Powered by Discuz! X3.4 |