Rbel12b/Lpf2

GitHub: Rbel12b/Lpf2

一个 ESP32 平台上的 LEGO PoweredUp Protocol 2 通信库,支持设备控制、Hub 连接与模拟功能。

Stars: 1 | Forks: 0

# esp32 的 Lpf2 库 ## 概述 Lpf2 是一个用于 ESP32 微控制器的 LEGO PoweredUp Protocol 2 (LPF2) 通信库。它为 LEGO 设备提供了设备通信、串口管理和设备抽象功能。该库支持本地 UART 通信以及与 LEGO Hub 的远程 BLE 通信,允许您以多种方式控制和交互 LEGO 设备。 从 platformio registry 下载该库:[rbel12b/Lpf2](https://registry.platformio.org/libraries/rbel12b/Lpf2) ## 免责声明 LEGO® 是 LEGO 集团公司的商标,该集团不赞助、授权或认可本项目。 ## 许可证 本库采用 GNU Affero General Public License v3.0 (AGPL-3.0) 许可。有关更多详细信息,请参阅 [LICENSE](./LICENSE) 文件。 ## 功能特性 - 通过 UART 进行串行通信 - 设备管理与检测 - 用于设备连接的端口抽象 - 控制 LEGO Hub - 模拟 Hub ## 硬件支持 目前仅支持 ESP32 微控制器,因为该库依赖于 FreeRTOS 功能和 Arduino 框架。不过,经过一些调整,应该可以将其移植到其他平台。 以下是我用于 esp32s3 的构建标志,其他变体可能需要进行调整: ``` ; I found that this core works better (latest commit at the time of writing). platform = https://github.com/platformio/platform-espressif32.git#3c076807e1f55b90799b50b946e76a0508e97778 board = esp32-s3-devkitc-1 build_flags = -std=gnu++2a ; C++ standard -Wformat ; format warnings -DCORE_DEBUG_LEVEL=0 ; disable esp32 core debug logs, so uart0 can be used to communicate with the devices without interference from the logs -DLPF2_LOG_LEVEL=4 ; the library uses the LPF2_LOG_x macros for logging, this sets the log level to debug (4) -DARDUINO_USB_CDC_ON_BOOT=1 ; enables the USB CDC serial port on boot -> logs will go to the USB serial port. -DCONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=1 -DCONFIG_ARDUHAL_LOG_COLORS=1 ; prertty logs -DBOARD_HAS_PSRAM=1 ; if your board has psram, the library itself doesn't require it. -DCONFIG_SPIRAM_USE=1 -mfix-esp32-psram-cache-issue build_unflags = -std=gnu++11 ; unflag the default C++11 standard, to avoid conflicts with the C++20 standard used by the library board_build.arduino.memory_type = qio_opi ; my board qio flash and opi psram, adjust if your board has a different flash or psram type board_build.psram_type = opi ; psram type ``` ## 库结构 ``` include/Lpf2/ ├── LWPConst.hpp # Protocol constants ├── Device.hpp # Base device interface, device factory ├── Port.hpp # Base Port class ├── DeviceManager.hpp # Device manager ├── Hub.hpp # LEGO Hub control ├── HubEmulation.hpp # LEGO Hub emulation ├── DeviceManager.hpp # Device manager ├── DeviceDescLib.hpp # Device descriptor library ├── Devices/ # Device implementations │ ├── BasicMotor.hpp │ ├── EncoderMotor.hpp │ ├── DistanceSensor.hpp │ └── ColorSensor.hpp ├── Local/ # Local port implementation │ └── Port.hpp ├── Remote/ # Remote port implementation │ └── Port.hpp ├── Virtual/ # Virtual (emulated) port implementation │ ├── Device.hpp │ └── Port.hpp ``` ## 功能 ### 设备通信 - 与 LEGO 设备进行的基于 UART 的串行通信 - LPF2 协议实现,用于可靠的设备消息传递 - 自动设备检测和枚举 ### 设备管理 - 动态设备发现和注册 - 用于协议信息的设备描述符库 - 用于创建设备实例的工厂模式 - 支持多种设备类型(电机、传感器等) ### 端口抽象 - 不同端口类型的统一接口 - 用于直接 UART 连接的本地端口支持 - 用于连接 Hub 的设备的远程端口支持 - 用于 Hub 模拟的虚拟端口支持 ### Hub 控制 - 连接 LEGO Hub 的 BLE 功能 - 命令执行和设备控制 - 实时设备状态监控 ### Hub 模拟 - 模拟 LEGO Hub - 响应 Hub 协议命令 - 支持虚拟设备定义 ### 设备支持 - 具有功率控制的基础电机 - 具有位置反馈的编码器电机 - 用于颜色检测的颜色传感器 - 用于距离测量的距离传感器 ## 操作指南 ### 使用示例 在 VSCode 中打开该库,并选择示例的 platformio enviroment。 ### 控制 Hub 示例: - [远程端口](examples/RemotePort/RemotePort.cpp) #### 连接 ``` Lpf2::Hub hub; // registers the default device descriptors, makes communication faster by using the built-in descriptor library, // instead of relying on the devices to send the information. Should be called once at startup. Lpf2::DeviceDescRegistry::registerDefault(); vTaskDelay(1); // for resetting the wdt in a loop if (!hub.isConnected() && !hub.isConnecting()) { hub.init(); vTaskDelay(500); } if (hub.isConnecting()) { hub.connectHub(); if (hub.isConnected()) { Serial.println("Connected to HUB"); } else { Serial.println("Failed to connect to HUB"); } } if (hub.isConnected()) { // Do anything with the hub. } ``` #### 使用远程端口 ``` // get the A port from a hub auto &portA = *hub.getPort(Lpf2::PortNum(Lpf2::ControlPlusHubPort::A)); portA.setMode(0); // only call it once per device connected, sets the mode (only needed when not using the default mode, wich is mode 0) portA.update(); if (portA.getDeviceType() == Lpf2::DeviceType::SIMPLE_MEDIUM_LINEAR_MOTOR) { portA.setPower(255, 0); // example motor power } else if (portA.getDeviceType() == Lpf2::DeviceType::TECHNIC_COLOR_SENSOR) { Serial.print("Color Idx: "); Serial.println(portA.getValue(0, 0)); } ``` 也可以配合远程端口[使用设备管理器](#device-manager) ### 本地端口 本地端口需要电机驱动器(H 桥),以及用于 ID 引脚的 47k 上拉电阻。 示例: - [本地端口](examples/LocalPort/LocalPort.cpp) #### 使用本地端口 ``` // Esp32IO is from an example, see examples/LocalPort/device.h Esp32IO portA_IO(1); // Use UART1 Lpf2::Local::Port portA(&portA_IO); // set up port to use the IO // Port pwm pins #define PORT_A_PWM_1 21 #define PORT_A_PWM_2 10 // Port ID pins #define PORT_A_ID_1 15 #define PORT_A_ID_2 16 // mcpwm unit and timer for the port #define PORT_A_PWM_UNIT mcpwm_unit_t(0) #define PORT_A_PWM_TIMER mcpwm_timer_t(0) #define initIOForPort(_port) \ port##_port##_IO.init(PORT_##_port##_ID_1, PORT_##_port##_ID_2, \ PORT_##_port##_PWM_1, PORT_##_port##_PWM_2, PORT_##_port##_PWM_UNIT, PORT_##_port##_PWM_TIMER, 1000); void setup() { Lpf2::DeviceDescRegistry::registerDefault(); // Initialize IO before the port, as the port will use the IO to communicate with the device initIOForPort(A); portA.init(); } void loop() { vTaskDelay(1); portA.update(); if (portA.getDeviceType() == Lpf2::DeviceType::SIMPLE_MEDIUM_LINEAR_MOTOR) { portA.setPower(255, 0); // example motor power } else if (portA.getDeviceType() == Lpf2::DeviceType::TECHNIC_COLOR_SENSOR) { Serial.print("Color Idx: "); Serial.println(portA.getValue(0, 0)); } } ``` ### 设备管理器 ``` Lpf2::DeviceRegistry::registerDefault(); // should be called once at startup, to register the default device factories. // port can be any class derived from Lpf2Port Lpf2::DeviceManager deviceManager(port); deviceManager.update(); // calls port.update(), checks device type, constructs device, should be called periodically. if (deviceManager.device()) { if (auto device = static_cast (deviceManager.device()->getCapability(Lpf2::Devices::TechnicColorSensor::CAP))) { Serial.print("Color idx: "); Serial.println(device->getColorIdx()); } if (auto device = static_cast (deviceManager.device()->getCapability(Lpf2::Devices::BasicMotor::CAP))) { device->setSpeed(-50); } else { // Device isn't a color sensor or a motor (all motors have BasicMotor::CAP) } } ``` ### 添加新设备 向设备管理器添加新设备很容易,您只需要创建一个头文件和一个源文件,如下所示: 头文件: ``` #pragma once #include "Lpf2/config.hpp" #include "Lpf2/Device.hpp" namespace Lpf2::Devices { class BasicMotorControl { public: virtual ~BasicMotorControl() = default; virtual void startPower(int speed) = 0; }; class BasicMotor : public Device, public BasicMotorControl { public: BasicMotor(Port &port) : Device(port) {} bool init() override { startPower(0); return true; } void poll() override { } const char *name() const override { return "DC Motor (dumb)"; } void startPower(int speed) override; bool hasCapability(DeviceCapabilityId id) const override; void *getCapability(DeviceCapabilityId id) override; inline static const DeviceCapabilityId CAP = Lpf2CapabilityRegistry::registerCapability("basic_motor"); static void registerFactory(DeviceRegistry ®); }; class BasicMotorFactory : public DeviceFactory { public: bool matches(Port &port) const override; Device *create(Port &port) const override { return new BasicMotor(port); } const char *name() const { return "Basic Motor Factory"; } }; }; // namespace Lpf2::Devices ``` 源文件: ``` #include "Lpf2/Devices/BasicMotor.hpp" namespace Lpf2::Devices { namespace { BasicMotorFactory factory; } void BasicMotor::registerFactory(DeviceRegistry ®) { reg.registerFactory(&factory); } void BasicMotor::startPower(int speed) { m_port.startPower(speed); } bool BasicMotor::hasCapability(DeviceCapabilityId id) const { return id == CAP; } void *BasicMotor::getCapability(DeviceCapabilityId id) { if (id == CAP) return static_cast(this); return nullptr; } bool BasicMotorFactory::matches(Port &port) const { switch (port.getDeviceType()) { case DeviceType::SIMPLE_MEDIUM_LINEAR_MOTOR: case DeviceType::TRAIN_MOTOR: return true; default: break; } return false; } }; // namespace Lpf2::Devices ``` 将名称替换为新设备的名称,向控制接口添加新函数,最重要的是:注册一个新的 capability(能力)。一个设备可以支持多个 capability,但它必须继承所有使用的 capability 接口,例如: ``` class EncoderMotor : public Device, public EncoderMotorControl, public BasicMotorControl {}; // then getCapability() becomes void *EncoderMotor::getCapability(DeviceCapabilityId id) { if (id == CAP) return static_cast(this); if (id == BasicMotor::CAP) return static_cast(this); return nullptr; } ``` ## 日志记录 该库使用 LPF2_LOG_x 宏进行日志记录 ``` build_flags = -DLPF2_LOG_LEVEL=4 ; used to set the log level (4 -> debug) ```
标签:Arduino, BLE, C++, ESP32, ESP32-S3, FreeRTOS, Hub模拟, IoT, LEGO, LPF2, PlatformIO, PoweredUp, Power Functions 2.0, UART, 串口通信, 乐高, 仿真, 传感器, 嵌入式开发, 开源库, 微控制器, 搜索引擎爬虫, 数据擦除, 智能玩具, 机器人, 物联网, 电机控制, 硬件控制, 蓝牙低功耗