GrabBag/Device/LedDisplayDevice/Src/LedDisplayDevice.cpp
2026-07-11 15:49:14 +08:00

742 lines
20 KiB
C++

#include "LedDisplayDevice.h"
#include <algorithm>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
#include "VrLog.h"
namespace {
constexpr uint8_t kFrameHead = 0xA5;
constexpr uint8_t kFrameTail = 0x5A;
constexpr uint16_t kSourceAddress = 0x8000;
constexpr uint8_t kProtocolVersion = 0x02;
constexpr uint8_t kCmdGroupRealtime = 0xA3;
constexpr uint8_t kCmdRealtimeArea = 0x06;
constexpr uint8_t kCmdGroupAck = 0xA0;
constexpr size_t kProtocolHeaderSize = 14;
constexpr size_t kMaxAreaPacketBytes = 1024;
#ifdef _WIN32
using SocketHandle = SOCKET;
SocketHandle InvalidSocket()
{
return INVALID_SOCKET;
}
bool SocketIsValid(SocketHandle socket)
{
return socket != INVALID_SOCKET;
}
void CloseSocket(SocketHandle socket)
{
if (SocketIsValid(socket)) {
closesocket(socket);
}
}
int LastSocketError()
{
return WSAGetLastError();
}
bool InitializeSocketLibrary()
{
static bool initialized = []() {
WSADATA data;
return WSAStartup(MAKEWORD(2, 2), &data) == 0;
}();
return initialized;
}
bool SetSocketBlocking(SocketHandle socket, bool blocking)
{
u_long mode = blocking ? 0 : 1;
return ioctlsocket(socket, FIONBIO, &mode) == 0;
}
#else
using SocketHandle = int;
SocketHandle InvalidSocket()
{
return -1;
}
bool SocketIsValid(SocketHandle socket)
{
return socket >= 0;
}
void CloseSocket(SocketHandle socket)
{
if (SocketIsValid(socket)) {
close(socket);
}
}
int LastSocketError()
{
return errno;
}
bool InitializeSocketLibrary()
{
return true;
}
bool SetSocketBlocking(SocketHandle socket, bool blocking)
{
int flags = fcntl(socket, F_GETFL, 0);
if (flags < 0) {
return false;
}
if (blocking) {
flags &= ~O_NONBLOCK;
} else {
flags |= O_NONBLOCK;
}
return fcntl(socket, F_SETFL, flags) == 0;
}
#endif
SocketHandle ToSocketHandle(intptr_t value)
{
return static_cast<SocketHandle>(value);
}
intptr_t FromSocketHandle(SocketHandle socket)
{
return static_cast<intptr_t>(socket);
}
std::string SocketErrorMessage(const char* operation, int code)
{
const char* operationName = "网络操作";
if (std::strcmp(operation, "socket") == 0) {
operationName = "创建套接字";
} else if (std::strcmp(operation, "set nonblocking") == 0) {
operationName = "设置非阻塞";
} else if (std::strcmp(operation, "connect") == 0) {
operationName = "连接";
} else if (std::strcmp(operation, "send") == 0) {
operationName = "发送";
} else if (std::strcmp(operation, "recv") == 0) {
operationName = "接收";
}
return std::string(operationName) + "失败,错误码:" + std::to_string(code);
}
uint8_t ClampByte(int value, int minValue = 0, int maxValue = 255)
{
return static_cast<uint8_t>((std::max)(minValue, (std::min)(maxValue, value)));
}
uint16_t ClampWord(int value, int minValue = 0, int maxValue = 0xFFFF)
{
return static_cast<uint16_t>((std::max)(minValue, (std::min)(maxValue, value)));
}
uint16_t PixelWord(int value)
{
return static_cast<uint16_t>(0x8000 | ClampWord(value, 0, 0x7FFF));
}
void AppendU16LE(std::vector<uint8_t>& data, uint16_t value)
{
data.push_back(static_cast<uint8_t>(value & 0xFF));
data.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
}
void AppendU32LE(std::vector<uint8_t>& data, uint32_t value)
{
data.push_back(static_cast<uint8_t>(value & 0xFF));
data.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
data.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
data.push_back(static_cast<uint8_t>((value >> 24) & 0xFF));
}
uint16_t ReadU16LE(const std::vector<uint8_t>& data, size_t offset)
{
return static_cast<uint16_t>(data[offset] | (static_cast<uint16_t>(data[offset + 1]) << 8));
}
uint16_t CalcCrc16(const uint8_t* data, size_t size)
{
uint16_t crc = 0;
for (size_t i = 0; i < size; ++i) {
crc ^= data[i];
for (int bit = 0; bit < 8; ++bit) {
if ((crc & 0x0001) != 0) {
crc = static_cast<uint16_t>((crc >> 1) ^ 0xA001);
} else {
crc = static_cast<uint16_t>(crc >> 1);
}
}
}
return crc;
}
uint16_t CalcCrc16(const std::vector<uint8_t>& data)
{
return CalcCrc16(data.data(), data.size());
}
void AppendEscaped(std::vector<uint8_t>& frame, uint8_t byte)
{
if (byte == 0xA5) {
frame.push_back(0xA6);
frame.push_back(0x02);
} else if (byte == 0xA6) {
frame.push_back(0xA6);
frame.push_back(0x01);
} else if (byte == 0x5A) {
frame.push_back(0x5B);
frame.push_back(0x02);
} else if (byte == 0x5B) {
frame.push_back(0x5B);
frame.push_back(0x01);
} else {
frame.push_back(byte);
}
}
std::vector<uint8_t> BuildFrame(const LedDisplayConfig& config, const std::vector<uint8_t>& data)
{
std::vector<uint8_t> packet;
packet.reserve(kProtocolHeaderSize + data.size() + 2);
AppendU16LE(packet, ClampWord(config.slaveId, 0, 0xFFFF));
AppendU16LE(packet, kSourceAddress);
packet.push_back(0x00);
packet.push_back(0x00);
packet.push_back(0x00);
packet.push_back(0x00);
packet.push_back(0x00);
packet.push_back(0x01);
packet.push_back(ClampByte(config.controllerType, 0, 0xFF));
packet.push_back(kProtocolVersion);
AppendU16LE(packet, ClampWord(static_cast<int>(data.size()), 0, 0xFFFF));
packet.insert(packet.end(), data.begin(), data.end());
const uint16_t crc = CalcCrc16(packet);
AppendU16LE(packet, crc);
std::vector<uint8_t> frame;
frame.reserve(packet.size() + 16);
for (int i = 0; i < 8; ++i) {
frame.push_back(kFrameHead);
}
for (uint8_t byte : packet) {
AppendEscaped(frame, byte);
}
frame.push_back(kFrameTail);
return frame;
}
bool UnescapeFramePayload(const std::vector<uint8_t>& rawFrame, std::vector<uint8_t>& payload)
{
if (rawFrame.size() < 10 || rawFrame.back() != kFrameTail) {
return false;
}
size_t begin = 0;
const size_t end = rawFrame.size() - 1;
while (begin < end && rawFrame[begin] == kFrameHead) {
++begin;
}
if (begin >= end) {
return false;
}
payload.clear();
for (size_t i = begin; i < end; ++i) {
const uint8_t byte = rawFrame[i];
if ((byte == 0xA6 || byte == 0x5B) && i + 1 < end) {
const uint8_t next = rawFrame[++i];
if (byte == 0xA6 && next == 0x02) {
payload.push_back(0xA5);
} else if (byte == 0xA6 && next == 0x01) {
payload.push_back(0xA6);
} else if (byte == 0x5B && next == 0x02) {
payload.push_back(0x5A);
} else if (byte == 0x5B && next == 0x01) {
payload.push_back(0x5B);
} else {
return false;
}
} else {
payload.push_back(byte);
}
}
return true;
}
bool SetSocketTimeout(SocketHandle socket, int timeoutMs)
{
#ifdef _WIN32
const DWORD timeout = static_cast<DWORD>((std::max)(1, timeoutMs));
return setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout)) == 0 &&
setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout)) == 0;
#else
timeval timeout;
timeout.tv_sec = (std::max)(1, timeoutMs) / 1000;
timeout.tv_usec = ((std::max)(1, timeoutMs) % 1000) * 1000;
return setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == 0 &&
setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) == 0;
#endif
}
bool WaitForSocket(SocketHandle socket, bool write, int timeoutMs)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(socket, &fds);
timeval timeout;
timeout.tv_sec = (std::max)(1, timeoutMs) / 1000;
timeout.tv_usec = ((std::max)(1, timeoutMs) % 1000) * 1000;
#ifdef _WIN32
const int nfds = 0;
#else
const int nfds = socket + 1;
#endif
const int ret = select(nfds, write ? nullptr : &fds, write ? &fds : nullptr, nullptr, &timeout);
return ret > 0 && FD_ISSET(socket, &fds);
}
bool ConnectSocket(const std::string& ip,
int port,
int timeoutMs,
SocketHandle& socket,
std::string& error)
{
socket = InvalidSocket();
if (!InitializeSocketLibrary()) {
error = "初始化套接字库失败";
return false;
}
sockaddr_in address;
std::memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(static_cast<uint16_t>(port));
if (inet_pton(AF_INET, ip.c_str(), &address.sin_addr) != 1) {
error = "显示屏网络地址无效:" + ip;
return false;
}
SocketHandle candidate = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (!SocketIsValid(candidate)) {
error = SocketErrorMessage("socket", LastSocketError());
return false;
}
if (!SetSocketBlocking(candidate, false)) {
error = SocketErrorMessage("set nonblocking", LastSocketError());
CloseSocket(candidate);
return false;
}
int ret = ::connect(candidate, reinterpret_cast<sockaddr*>(&address), sizeof(address));
if (ret != 0) {
const int code = LastSocketError();
#ifdef _WIN32
const bool inProgress = code == WSAEWOULDBLOCK || code == WSAEINPROGRESS || code == WSAEINVAL;
#else
const bool inProgress = code == EINPROGRESS || code == EWOULDBLOCK;
#endif
if (!inProgress || !WaitForSocket(candidate, true, timeoutMs)) {
error = SocketErrorMessage("connect", code);
CloseSocket(candidate);
return false;
}
int socketError = 0;
#ifdef _WIN32
int socketErrorLen = sizeof(socketError);
#else
socklen_t socketErrorLen = sizeof(socketError);
#endif
if (getsockopt(candidate, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&socketError), &socketErrorLen) != 0 ||
socketError != 0) {
error = SocketErrorMessage("connect", socketError == 0 ? LastSocketError() : socketError);
CloseSocket(candidate);
return false;
}
}
SetSocketBlocking(candidate, true);
SetSocketTimeout(candidate, timeoutMs);
socket = candidate;
return true;
}
bool SendAll(SocketHandle socket, const std::vector<uint8_t>& data, std::string& error)
{
size_t sent = 0;
while (sent < data.size()) {
const int chunk = static_cast<int>(std::min<size_t>(data.size() - sent, 4096));
const int ret = send(socket, reinterpret_cast<const char*>(data.data() + sent), chunk, 0);
if (ret <= 0) {
error = SocketErrorMessage("send", LastSocketError());
return false;
}
sent += static_cast<size_t>(ret);
}
return true;
}
bool ReceiveRawFrame(SocketHandle socket, int timeoutMs, std::vector<uint8_t>& rawFrame, std::string& error)
{
rawFrame.clear();
bool started = false;
while (rawFrame.size() < 4096) {
if (!WaitForSocket(socket, false, timeoutMs)) {
error = "接收超时";
return false;
}
uint8_t byte = 0;
const int ret = recv(socket, reinterpret_cast<char*>(&byte), 1, 0);
if (ret <= 0) {
error = ret == 0 ? "显示屏关闭连接" : SocketErrorMessage("recv", LastSocketError());
return false;
}
if (!started) {
if (byte != kFrameHead) {
continue;
}
started = true;
}
rawFrame.push_back(byte);
if (byte == kFrameTail) {
return true;
}
}
error = "响应帧过大";
return false;
}
const char* AckErrorToString(uint8_t error)
{
switch (error) {
case 0: return "无错误";
case 1: return "命令组错误";
case 2: return "命令不存在";
case 3: return "控制器忙";
case 4: return "内存容量超限";
case 5: return "校验错误";
case 6: return "文件不存在";
case 7: return "闪存访问错误";
case 8: return "文件下载错误";
case 9: return "文件名错误";
case 10: return "文件类型错误";
case 11: return "文件校验错误";
case 12: return "字库不存在";
case 13: return "固件类型不匹配";
case 14: return "日期时间格式错误";
case 15: return "文件已存在";
case 16: return "文件块编号错误";
default: return "未知控制器错误";
}
}
} // namespace
int ILedDisplayDevice::CreateObject(ILedDisplayDevice** ppDevice)
{
if (!ppDevice) {
return -1;
}
*ppDevice = new CLedDisplayDevice();
return *ppDevice ? 0 : -2;
}
CLedDisplayDevice::CLedDisplayDevice() = default;
CLedDisplayDevice::~CLedDisplayDevice()
{
Close();
}
int CLedDisplayDevice::Open(const LedDisplayConfig& config)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_config = config;
CloseSocketLocked();
if (!m_config.enabled) {
NotifyStatus(ELedDisplayStatus::Disconnected, "显示屏未启用");
return 0;
}
if (m_config.ip.empty() || m_config.port <= 0 || m_config.port > 65535) {
NotifyStatus(ELedDisplayStatus::Error, "显示屏网络配置无效");
return -1;
}
if (!ConnectLocked()) {
NotifyStatus(ELedDisplayStatus::Error, m_lastError);
return -2;
}
NotifyStatus(ELedDisplayStatus::Connected, "显示屏已连接");
return 0;
}
void CLedDisplayDevice::Close()
{
std::lock_guard<std::mutex> lock(m_mutex);
CloseSocketLocked();
}
bool CLedDisplayDevice::IsConnected() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_connected && SocketIsValid(ToSocketHandle(m_socket));
}
int CLedDisplayDevice::SendResult(const LedDisplayResult& result)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_config.enabled) {
return 0;
}
if (!ConnectLocked()) {
NotifyStatus(ELedDisplayStatus::Error, m_lastError);
return -1;
}
const std::vector<uint8_t> data = BuildRealtimeData(result);
if (data.empty()) {
NotifyStatus(ELedDisplayStatus::Error, "生成显示屏实时数据失败");
return -2;
}
if (!SendFrameLocked(data)) {
LOG_ERROR("LED display send failed: %s\n", m_lastError.c_str());
NotifyStatus(ELedDisplayStatus::Error, m_lastError);
CloseSocketLocked();
return -3;
}
if (!ReceiveAckLocked()) {
LOG_ERROR("LED display ack failed: %s\n", m_lastError.c_str());
NotifyStatus(ELedDisplayStatus::Error, m_lastError);
CloseSocketLocked();
return -4;
}
return 0;
}
const LedDisplayConfig& CLedDisplayDevice::GetConfig() const
{
return m_config;
}
void CLedDisplayDevice::SetStatusCallback(LedDisplayStatusCallback callback, void* user)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_statusCallback = callback;
m_statusUser = user;
}
void CLedDisplayDevice::NotifyStatus(ELedDisplayStatus status, const std::string& message)
{
if (m_statusCallback) {
m_statusCallback(status, message, m_statusUser);
}
}
bool CLedDisplayDevice::ConnectLocked()
{
if (m_connected && SocketIsValid(ToSocketHandle(m_socket))) {
return true;
}
CloseSocketLocked();
SocketHandle socket = InvalidSocket();
std::string error;
if (!ConnectSocket(m_config.ip, m_config.port, (std::max)(100, m_config.timeoutMs), socket, error)) {
SetLastError(error);
return false;
}
m_socket = FromSocketHandle(socket);
m_connected = true;
return true;
}
void CLedDisplayDevice::CloseSocketLocked()
{
SocketHandle socket = ToSocketHandle(m_socket);
if (SocketIsValid(socket)) {
CloseSocket(socket);
}
m_socket = FromSocketHandle(InvalidSocket());
m_connected = false;
}
bool CLedDisplayDevice::SendFrameLocked(const std::vector<uint8_t>& data)
{
const std::vector<uint8_t> frame = BuildFrame(m_config, data);
std::string error;
if (!SendAll(ToSocketHandle(m_socket), frame, error)) {
SetLastError(error);
return false;
}
return true;
}
bool CLedDisplayDevice::ReceiveAckLocked()
{
std::vector<uint8_t> rawFrame;
std::string error;
if (!ReceiveRawFrame(ToSocketHandle(m_socket), (std::max)(100, m_config.timeoutMs), rawFrame, error)) {
SetLastError(error);
return false;
}
std::vector<uint8_t> payload;
if (!UnescapeFramePayload(rawFrame, payload)) {
SetLastError("显示屏响应帧无效");
return false;
}
if (payload.size() < kProtocolHeaderSize + 5 + 2) {
SetLastError("显示屏响应长度过短");
return false;
}
const uint16_t expectedCrc = ReadU16LE(payload, payload.size() - 2);
const uint16_t actualCrc = CalcCrc16(payload.data(), payload.size() - 2);
if (expectedCrc != actualCrc) {
SetLastError("显示屏响应校验不一致");
return false;
}
const size_t dataOffset = kProtocolHeaderSize;
const uint8_t cmdGroup = payload[dataOffset];
const uint8_t cmd = payload[dataOffset + 1];
const uint8_t cmdError = payload[dataOffset + 2];
if (cmdGroup != kCmdGroupAck) {
SetLastError("显示屏响应命令组异常");
return false;
}
if (cmd == 0x00 && cmdError == 0x00) {
return true;
}
SetLastError(std::string("显示屏拒绝命令:") + AckErrorToString(cmdError) +
" (" + std::to_string(cmdError) + ")");
return false;
}
std::vector<uint8_t> CLedDisplayDevice::BuildRealtimeData(const LedDisplayResult& result) const
{
std::string text = BuildResultText(result);
std::vector<uint8_t> textData(text.begin(), text.end());
const size_t fixedAreaBytes = 27;
if (textData.size() + fixedAreaBytes > kMaxAreaPacketBytes) {
textData.resize(kMaxAreaPacketBytes - fixedAreaBytes);
}
std::vector<uint8_t> area;
area.reserve(fixedAreaBytes + textData.size());
area.push_back(0x00);
AppendU16LE(area, PixelWord(m_config.areaX));
AppendU16LE(area, ClampWord(m_config.areaY, 0, 0x7FFF));
AppendU16LE(area, PixelWord(m_config.areaWidth > 0 ? m_config.areaWidth : 64));
AppendU16LE(area, ClampWord(m_config.areaHeight > 0 ? m_config.areaHeight : 32, 1, 0x7FFF));
area.push_back(ClampByte(m_config.startAddress, 0, 31));
area.push_back(0x00);
area.push_back(0x00);
AppendU16LE(area, ClampWord(m_config.dynamicTimeoutSec, 0, 0xFFFF));
area.push_back(0x00);
area.push_back(0x00);
area.push_back(0x00);
area.push_back(0x02);
area.push_back(0x01);
area.push_back(ClampByte(m_config.displayMode, 0x01, 0x07));
area.push_back(0x00);
area.push_back(ClampByte(m_config.speed, 0, 0x18));
area.push_back(ClampByte(m_config.stayTime, 0, 0xFF));
AppendU32LE(area, static_cast<uint32_t>(textData.size()));
area.insert(area.end(), textData.begin(), textData.end());
std::vector<uint8_t> data;
data.reserve(11 + area.size());
data.push_back(kCmdGroupRealtime);
data.push_back(kCmdRealtimeArea);
data.push_back(0x01);
data.push_back(0x00);
data.push_back(0x00);
data.push_back(0x00);
data.push_back(0x00);
data.push_back(0x00);
data.push_back(0x01);
AppendU16LE(data, ClampWord(static_cast<int>(area.size()), 0, 0xFFFF));
data.insert(data.end(), area.begin(), area.end());
return data;
}
std::string CLedDisplayDevice::BuildResultText(const LedDisplayResult& result) const
{
if (!result.valid) {
return "NO DATA";
}
char buffer[128] = {0};
const char* state = result.guideCode == 2 ? "ERROR" : "OK";
std::snprintf(buffer,
sizeof(buffer),
"%s\nD:%.0f L:%.0f\nA:%.1f",
state,
result.distance,
result.lateralOffset,
result.angle);
return buffer;
}
void CLedDisplayDevice::SetLastError(const std::string& error)
{
m_lastError = error;
}