// led/Bx6k1YyLedClient.cxx #include "Bx6k1YyLedClient.hpp" #include "BX6K1YYProtocol.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(-1)) return false; size_t inLeft = in.size(); size_t outLeft = in.size() * 4 + 16; std::vector out(outLeft); char* inBuf = const_cast(in.data()); char* outBuf = out.data(); size_t res = iconv(cd, &inBuf, &inLeft, &outBuf, &outLeft); iconv_close(cd); if (res == static_cast(-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(&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(n); } return true; } // 收到至少一个 0x5A 字节或超时 bool recvUntilTail(int fd, std::vector& 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( std::chrono::duration_cast(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 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 phy1; std::vector 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 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 ack; recvUntilTail(m_fd, ack, m_cfg.responseTimeoutMs); // 吃掉 ACK // [诊断] 解 ACK 帧并把 PHY1 全打出来,看控制卡是成功还是返回错误码 if (!ack.empty()) { std::vector 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]=ErrorCode,0=成功 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