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