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, 串口通信, 乐高, 仿真, 传感器, 嵌入式开发, 开源库, 微控制器, 搜索引擎爬虫, 数据擦除, 智能玩具, 机器人, 物联网, 电机控制, 硬件控制, 蓝牙低功耗