You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
7.6 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// led/Bx6k1YyLedClient.cxx
#include "Bx6k1YyLedClient.hpp"
#include "BX6K1YYProtocol.hpp"
#include <arpa/inet.h>
#include <cerrno>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <iconv.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <vector>
namespace led {
namespace {
bool encodeUtf8(const std::string& in, const std::string& encoding, std::string& bytes) {
if (encoding.empty() || encoding == "UTF-8" || encoding == "utf-8") {
bytes = in;
return true;
}
iconv_t cd = iconv_open(encoding.c_str(), "UTF-8//IGNORE");
if (cd == reinterpret_cast<iconv_t>(-1)) return false;
size_t inLeft = in.size();
size_t outLeft = in.size() * 4 + 16;
std::vector<char> out(outLeft);
char* inBuf = const_cast<char*>(in.data());
char* outBuf = out.data();
size_t res = iconv(cd, &inBuf, &inLeft, &outBuf, &outLeft);
iconv_close(cd);
if (res == static_cast<size_t>(-1)) return false;
bytes.assign(out.data(), out.size() - outLeft);
return true;
}
bool setNonBlocking(int fd, bool nb) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return false;
return fcntl(fd, F_SETFL, nb ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)) == 0;
}
bool connectWithTimeout(int fd, const sockaddr_in& addr, int timeoutMs) {
int rc = ::connect(fd, reinterpret_cast<const sockaddr*>(&addr), sizeof(addr));
if (rc == 0) return true;
if (errno != EINPROGRESS) return false;
pollfd pfd{fd, POLLOUT, 0};
if (::poll(&pfd, 1, timeoutMs) <= 0) return false;
int err = 0; socklen_t len = sizeof(err);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) return false;
return err == 0;
}
bool sendAll(int fd, const uint8_t* data, size_t len, int timeoutMs) {
size_t sent = 0;
while (sent < len) {
pollfd pfd{fd, POLLOUT, 0};
if (::poll(&pfd, 1, timeoutMs) <= 0) return false;
ssize_t n = ::send(fd, data + sent, len - sent, MSG_NOSIGNAL);
if (n <= 0) return false;
sent += static_cast<size_t>(n);
}
return true;
}
// 收到至少一个 0x5A 字节或超时
bool recvUntilTail(int fd, std::vector<uint8_t>& buf, int timeoutMs) {
uint8_t chunk[1024];
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs);
while (true) {
auto now = std::chrono::steady_clock::now();
if (now >= deadline) return !buf.empty();
int wait = static_cast<int>(
std::chrono::duration_cast<std::chrono::milliseconds>(deadline - now).count());
pollfd pfd{fd, POLLIN, 0};
if (::poll(&pfd, 1, wait) <= 0) return !buf.empty();
ssize_t n = ::recv(fd, chunk, sizeof(chunk), 0);
if (n <= 0) return !buf.empty();
buf.insert(buf.end(), chunk, chunk + n);
if (std::find(buf.begin(), buf.end(), 0x5A) != buf.end()) return true;
}
}
} // anonymous namespace
Bx6k1YyLedClient::Bx6k1YyLedClient(Config cfg) : m_cfg(std::move(cfg)) {}
Bx6k1YyLedClient::~Bx6k1YyLedClient() { closeSocket(); }
void Bx6k1YyLedClient::closeSocket() {
if (m_fd >= 0) { ::close(m_fd); m_fd = -1; }
}
bool Bx6k1YyLedClient::ensureConnected(std::string& error) {
if (m_fd >= 0) return true;
int fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) { error = std::string("socket: ") + std::strerror(errno); return false; }
setNonBlocking(fd, true);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(m_cfg.port);
if (inet_pton(AF_INET, m_cfg.ip.c_str(), &addr.sin_addr) != 1) {
::close(fd);
error = "bad ip " + m_cfg.ip;
return false;
}
if (!connectWithTimeout(fd, addr, m_cfg.connectTimeoutMs)) {
::close(fd);
error = "connect " + m_cfg.ip + ":" + std::to_string(m_cfg.port) + " failed";
return false;
}
setNonBlocking(fd, false);
int one = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
m_fd = fd;
return true;
}
bool Bx6k1YyLedClient::queryScreenSize(int& outW, int& outH, std::string& error) {
if (m_fd < 0) { error = "not connected"; return false; }
auto frame = buildScreenParameterQueryFrame();
if (!sendAll(m_fd, frame.data(), frame.size(), m_cfg.responseTimeoutMs)) {
error = "send screen query failed"; return false;
}
std::vector<uint8_t> buf;
if (!recvUntilTail(m_fd, buf, m_cfg.responseTimeoutMs)) {
error = "recv screen query timeout"; return false;
}
auto head = std::find(buf.begin(), buf.end(), 0xA5);
if (head == buf.end()) { error = "no head"; return false; }
auto tailIt = std::find(buf.rbegin(), buf.rend(), 0x5A);
if (tailIt == buf.rend()) { error = "no tail"; return false; }
std::vector<uint8_t> phy1;
std::vector<uint8_t> oneFrame(head, tailIt.base());
if (!decodeFrame(oneFrame, phy1, error)) return false;
return parseScreenDimensions(phy1, outW, outH, error);
}
bool Bx6k1YyLedClient::displayText(const std::string& utf8Text, int areaId, std::string& error) {
if (!ensureConnected(error)) return false;
if (m_screenW <= 0 || m_screenH <= 0) {
int w = 0, h = 0;
if (queryScreenSize(w, h, error)) {
m_screenW = w; m_screenH = h;
} else {
// 查询失败:使用回退分辨率,不阻塞刷新
m_screenW = m_cfg.fallbackScreenWidth;
m_screenH = m_cfg.fallbackScreenHeight;
error.clear();
}
}
std::string encoded;
if (!encodeUtf8(utf8Text, m_cfg.textEncoding, encoded)) {
error = "encode failed"; return false;
}
const int lineH = m_screenH; // 单行:使用整屏高度
const bool isTemporary = (areaId == kAreaTemporary);
const int timeoutSec = isTemporary ? kTemporaryTimeoutSec : 0;
const int effectiveArea = (areaId == kAreaDefault) ? kAreaDefault : kAreaTemporary;
std::vector<uint8_t> frame = buildDynamicTextFrame(encoded,
m_screenW, lineH,
effectiveArea,
isTemporary, timeoutSec);
if (frame.empty()) { error = "build frame failed"; return false; }
if (!sendAll(m_fd, frame.data(), frame.size(), m_cfg.responseTimeoutMs)) {
error = "send frame failed"; return false;
}
std::vector<uint8_t> ack;
recvUntilTail(m_fd, ack, m_cfg.responseTimeoutMs); // 吃掉 ACK
// [诊断] 解 ACK 帧并把 PHY1 全打出来,看控制卡是成功还是返回错误码
if (!ack.empty()) {
std::vector<uint8_t> phy1;
std::string decErr;
if (decodeFrame(ack, phy1, decErr)) {
std::fprintf(stderr, "[Led] ACK phy1 (%zu B):", phy1.size());
for (uint8_t b : phy1) std::fprintf(stderr, " %02X", b);
std::fprintf(stderr, "\n");
// BX-6K1-YY ACK payload 通常从 phy1[14] 起payload[5]=ErrorCode0=成功
if (phy1.size() >= 20) {
std::fprintf(stderr, "[Led] ACK payload[5] (ErrorCode) = 0x%02X\n",
phy1[14 + 5]);
}
} else {
std::fprintf(stderr, "[Led] ACK decode failed: %s, raw (%zu B):",
decErr.c_str(), ack.size());
for (uint8_t b : ack) std::fprintf(stderr, " %02X", b);
std::fprintf(stderr, "\n");
}
} else {
std::fprintf(stderr, "[Led] ACK empty (no response from controller)\n");
}
return true;
}
} // namespace led