generated from guanyuankai/bonus-edge-proxy
feat(battery): ADD Battery Controller.
This commit is contained in:
parent
e91238d114
commit
4302cf6d4f
|
|
@ -0,0 +1,352 @@
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// --- 协议常量定义 (需与 STM32 保持一致) ---
|
||||||
|
#define FRAME_HEAD 0xA5
|
||||||
|
#define CMD_REPORT 0x01
|
||||||
|
#define CMD_SET_MODE 0x02
|
||||||
|
#define CMD_SET_ALARM 0x03
|
||||||
|
#define CMD_ACK 0xAA
|
||||||
|
|
||||||
|
// --- 数据结构定义 (字节对齐) ---
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct BatteryPacket_t {
|
||||||
|
uint8_t battery_cap; // 1. 电量百分比
|
||||||
|
uint16_t time_to_full; // 2. 预测充满时间
|
||||||
|
uint16_t time_to_empty; // 3. 预测放电时间
|
||||||
|
uint8_t battery_status; // 4. 电池状态
|
||||||
|
int16_t temperature; // 5. 温度
|
||||||
|
uint8_t system_mode; // 6. 模式
|
||||||
|
uint8_t alarm_flag; // 7. 报警标志
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
// --- 简单的串口封装类 ---
|
||||||
|
class SerialPort {
|
||||||
|
public:
|
||||||
|
SerialPort(const std::string& device, int baudRate) : fd_(-1) {
|
||||||
|
openPort(device, baudRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
~SerialPort() {
|
||||||
|
if (fd_ >= 0)
|
||||||
|
close(fd_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isOpen() const {
|
||||||
|
return fd_ >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int writeData(const uint8_t* data, size_t len) {
|
||||||
|
if (fd_ < 0)
|
||||||
|
return -1;
|
||||||
|
return write(fd_, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int readData(uint8_t* buffer, size_t maxLen) {
|
||||||
|
if (fd_ < 0)
|
||||||
|
return -1;
|
||||||
|
return read(fd_, buffer, maxLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int fd_;
|
||||||
|
|
||||||
|
void openPort(const std::string& device, int baudRate) {
|
||||||
|
fd_ = open(device.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
|
||||||
|
if (fd_ < 0) {
|
||||||
|
perror("Failed to open serial port");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复阻塞模式 (或者根据需要保持非阻塞)
|
||||||
|
fcntl(fd_, F_SETFL, 0);
|
||||||
|
|
||||||
|
struct termios options;
|
||||||
|
tcgetattr(fd_, &options);
|
||||||
|
|
||||||
|
// 设置波特率
|
||||||
|
speed_t speed;
|
||||||
|
switch (baudRate) {
|
||||||
|
case 9600:
|
||||||
|
speed = B9600;
|
||||||
|
break;
|
||||||
|
case 115200:
|
||||||
|
speed = B115200;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
speed = B115200;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cfsetispeed(&options, speed);
|
||||||
|
cfsetospeed(&options, speed);
|
||||||
|
|
||||||
|
// 配置为 8N1, Raw 模式
|
||||||
|
options.c_cflag |= (CLOCAL | CREAD); // 启用接收
|
||||||
|
options.c_cflag &= ~PARENB; // 无校验
|
||||||
|
options.c_cflag &= ~CSTOPB; // 1停止位
|
||||||
|
options.c_cflag &= ~CSIZE;
|
||||||
|
options.c_cflag |= CS8; // 8数据位
|
||||||
|
options.c_cflag &= ~CRTSCTS; // 无流控
|
||||||
|
|
||||||
|
// 原始输入输出模式 (禁止回显、信号处理等)
|
||||||
|
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
|
||||||
|
options.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁止软件流控
|
||||||
|
options.c_iflag &= ~(ICRNL | INLCR); // 禁止CR/LF转换
|
||||||
|
options.c_oflag &= ~OPOST; // 原始输出
|
||||||
|
|
||||||
|
tcsetattr(fd_, TCSANOW, &options);
|
||||||
|
tcflush(fd_, TCIOFLUSH);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 通信协议处理类 ---
|
||||||
|
class CommProtocol {
|
||||||
|
public:
|
||||||
|
CommProtocol(SerialPort* serial) : serial_(serial), running_(false) {}
|
||||||
|
|
||||||
|
~CommProtocol() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动接收线程
|
||||||
|
void start() {
|
||||||
|
running_ = true;
|
||||||
|
rxThread_ = std::thread(&CommProtocol::receiveLoop, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
running_ = false;
|
||||||
|
if (rxThread_.joinable())
|
||||||
|
rxThread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送指令:设置模式
|
||||||
|
void sendSetMode(uint8_t mode) {
|
||||||
|
uint8_t payload = mode;
|
||||||
|
sendPacket(CMD_SET_MODE, &payload, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送指令:设置报警
|
||||||
|
void sendSetAlarm(uint8_t alarm) {
|
||||||
|
uint8_t payload = alarm;
|
||||||
|
sendPacket(CMD_SET_ALARM, &payload, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SerialPort* serial_;
|
||||||
|
std::thread rxThread_;
|
||||||
|
std::atomic<bool> running_;
|
||||||
|
std::vector<uint8_t> rxBuffer_; // 接收缓冲区
|
||||||
|
|
||||||
|
// CRC32 计算 (对应 STM32 HAL_CRC_Calculate + XOR)
|
||||||
|
// 注意:这里使用的是标准 CRC32 算法 (Polynomial 0x04C11DB7)
|
||||||
|
// 如果 STM32 端是默认配置,这应该能对上。
|
||||||
|
uint32_t calculateCRC32(const uint8_t* data, size_t len) {
|
||||||
|
uint32_t crc = 0xFFFFFFFF; // Initial Value
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
uint32_t byte = data[i];
|
||||||
|
// 标准 CRC32 是反转输入和输出的,这里模拟 STM32 常见配置
|
||||||
|
// 这里使用最通用的查表法或者位移法
|
||||||
|
// 为简化代码,使用位移法 (效率较低但足够)
|
||||||
|
|
||||||
|
// 注意:STM32 硬件CRC如果不开启 REVERSE_INPUT/OUTPUT,行为是 BigEndian 风格
|
||||||
|
// 但你在 STM32 代码里写了 `crc ^ 0xFFFFFFFF`,通常是为了匹配标准 CRC32
|
||||||
|
// 下面这个是标准 Ethernet CRC32 算法:
|
||||||
|
crc = crc ^ byte;
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
if (crc & 1)
|
||||||
|
crc = (crc >> 1) ^ 0xEDB88320;
|
||||||
|
else
|
||||||
|
crc = crc >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc ^ 0xFFFFFFFF;
|
||||||
|
|
||||||
|
// **注意**:如果发现校验一直不过,STM32 硬件CRC可能没有开启 Input/Output Reverse。
|
||||||
|
// 如果是那样,请尝试替换为非反转的 CRC32 算法。
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果上面的 CRC 算不对,试试这个 STM32 默认硬件CRC (无反转) 的版本:
|
||||||
|
/*
|
||||||
|
uint32_t calculateCRC32_STM32_Default(const uint8_t* data, size_t len) {
|
||||||
|
uint32_t crc = 0xFFFFFFFF;
|
||||||
|
for(size_t i=0; i<len; i++) {
|
||||||
|
crc = crc ^ (uint32_t)data[i] << 24;
|
||||||
|
for(int j=0; j<8; j++) {
|
||||||
|
if(crc & 0x80000000) crc = (crc << 1) ^ 0x04C11DB7;
|
||||||
|
else crc = crc << 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc; // STM32 代码有 ^ 0xFFFFFFFF 就加上,没有就不加
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void sendPacket(uint8_t cmd, const uint8_t* payload, uint8_t len) {
|
||||||
|
std::vector<uint8_t> packet;
|
||||||
|
packet.push_back(FRAME_HEAD);
|
||||||
|
packet.push_back(cmd);
|
||||||
|
packet.push_back(len);
|
||||||
|
|
||||||
|
if (len > 0 && payload != nullptr) {
|
||||||
|
packet.insert(packet.end(), payload, payload + len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算 CRC (Header + Cmd + Len + Payload)
|
||||||
|
uint32_t crc = calculateCRC32(packet.data(), packet.size());
|
||||||
|
|
||||||
|
// 小端序发送 CRC
|
||||||
|
packet.push_back((crc >> 0) & 0xFF);
|
||||||
|
packet.push_back((crc >> 8) & 0xFF);
|
||||||
|
packet.push_back((crc >> 16) & 0xFF);
|
||||||
|
packet.push_back((crc >> 24) & 0xFF);
|
||||||
|
|
||||||
|
serial_->writeData(packet.data(), packet.size());
|
||||||
|
|
||||||
|
std::cout << "[Tx] Cmd: " << std::hex << (int)cmd << " Len: " << (int)len << std::dec
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void processFrame(const uint8_t* frame, size_t totalLen) {
|
||||||
|
uint8_t cmd = frame[1];
|
||||||
|
// uint8_t len = frame[2]; // payload length
|
||||||
|
const uint8_t* payload = &frame[3];
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case CMD_REPORT: {
|
||||||
|
if (totalLen >= sizeof(BatteryPacket_t) + 7) {
|
||||||
|
BatteryPacket_t data;
|
||||||
|
memcpy(&data, payload, sizeof(BatteryPacket_t));
|
||||||
|
|
||||||
|
std::cout << "\n=== STM32 Status Report ===" << std::endl;
|
||||||
|
std::cout << "Cap: " << (int)data.battery_cap << "%" << std::endl;
|
||||||
|
std::cout << "Time Full: " << data.time_to_full << " min" << std::endl;
|
||||||
|
std::cout << "Time Empty: " << data.time_to_empty << " min" << std::endl;
|
||||||
|
std::cout << "Status: " << (int)data.battery_status << std::endl;
|
||||||
|
std::cout << "Temp: " << (float)data.temperature / 10.0f << " C" << std::endl;
|
||||||
|
std::cout << "Mode: " << (int)data.system_mode << std::endl;
|
||||||
|
std::cout << "Alarm: " << (int)data.alarm_flag << std::endl;
|
||||||
|
|
||||||
|
// 回复 ACK
|
||||||
|
uint8_t ack[] = "OK";
|
||||||
|
sendPacket(CMD_ACK, ack, 2);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CMD_ACK:
|
||||||
|
std::cout << "[Rx] Received ACK from STM32" << std::endl;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::cout << "[Rx] Unknown CMD: " << std::hex << (int)cmd << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void receiveLoop() {
|
||||||
|
uint8_t buf[128];
|
||||||
|
while (running_) {
|
||||||
|
int n = serial_->readData(buf, sizeof(buf));
|
||||||
|
if (n > 0) {
|
||||||
|
// 将新数据追加到 vector 缓冲区
|
||||||
|
rxBuffer_.insert(rxBuffer_.end(), buf, buf + n);
|
||||||
|
|
||||||
|
// 尝试解析帧
|
||||||
|
while (rxBuffer_.size() >= 7) { // 最小帧长 Header+Cmd+Len+CRC(4) = 7
|
||||||
|
// 1. 寻找帧头
|
||||||
|
if (rxBuffer_[0] != FRAME_HEAD) {
|
||||||
|
rxBuffer_.erase(rxBuffer_.begin()); // 丢弃无效字节
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t dataLen = rxBuffer_[2];
|
||||||
|
size_t frameLen = 3 + dataLen + 4;
|
||||||
|
|
||||||
|
// 2. 检查缓冲区是否有完整的一帧
|
||||||
|
if (rxBuffer_.size() < frameLen) {
|
||||||
|
break; // 数据不够,等待下次接收
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 提取 CRC 并校验
|
||||||
|
uint32_t receivedCRC = 0;
|
||||||
|
receivedCRC |= rxBuffer_[frameLen - 4] << 0;
|
||||||
|
receivedCRC |= rxBuffer_[frameLen - 3] << 8;
|
||||||
|
receivedCRC |= rxBuffer_[frameLen - 2] << 16;
|
||||||
|
receivedCRC |= rxBuffer_[frameLen - 1] << 24;
|
||||||
|
|
||||||
|
uint32_t calcCRC = calculateCRC32(rxBuffer_.data(), frameLen - 4);
|
||||||
|
|
||||||
|
if (calcCRC == receivedCRC) {
|
||||||
|
// 校验通过,处理帧
|
||||||
|
processFrame(rxBuffer_.data(), frameLen);
|
||||||
|
// 移除已处理的帧
|
||||||
|
rxBuffer_.erase(rxBuffer_.begin(), rxBuffer_.begin() + frameLen);
|
||||||
|
} else {
|
||||||
|
std::cerr << "CRC Error! Calc: " << std::hex << calcCRC
|
||||||
|
<< " Recv: " << receivedCRC << std::endl;
|
||||||
|
// 校验失败,移除头字节,尝试重新寻找下一帧
|
||||||
|
rxBuffer_.erase(rxBuffer_.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// 1. 配置串口设备 (请根据实际情况修改,例如 /dev/ttyUSB0 或 /dev/ttyS4)
|
||||||
|
// RK3588 的硬件串口通常是 /dev/ttyS0 ~ /dev/ttyS9,或者 /dev/ttyFIQ0
|
||||||
|
std::string devPath = "/dev/ttyS4";
|
||||||
|
|
||||||
|
std::cout << "Opening serial port: " << devPath << std::endl;
|
||||||
|
SerialPort serial(devPath, 115200);
|
||||||
|
|
||||||
|
if (!serial.isOpen()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 启动协议处理
|
||||||
|
CommProtocol protocol(&serial);
|
||||||
|
protocol.start();
|
||||||
|
|
||||||
|
// 3. 交互循环
|
||||||
|
while (true) {
|
||||||
|
std::cout << "\nChoose Action:\n1. Set Mode WORK\n2. Set Mode SLEEP\n3. Set Alarm ON\n4. "
|
||||||
|
"Set Alarm OFF\nInput: ";
|
||||||
|
int choice;
|
||||||
|
std::cin >> choice;
|
||||||
|
|
||||||
|
switch (choice) {
|
||||||
|
case 1:
|
||||||
|
protocol.sendSetMode(1); // MODE_WORK
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
protocol.sendSetMode(2); // MODE_SLEEP
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
protocol.sendSetAlarm(1);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
protocol.sendSetAlarm(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单延时防止刷屏
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue