// led/BX6K1YYProtocol.cxx #include "BX6K1YYProtocol.hpp" #include namespace led { namespace { inline void appendLe16(std::vector& v, uint16_t value) { v.push_back(static_cast(value & 0xff)); v.push_back(static_cast((value >> 8) & 0xff)); } inline void appendLe32(std::vector& v, uint32_t value) { v.push_back(static_cast(value & 0xff)); v.push_back(static_cast((value >> 8) & 0xff)); v.push_back(static_cast((value >> 16) & 0xff)); v.push_back(static_cast((value >> 24) & 0xff)); } std::vector escape(const std::vector& in) { std::vector out; out.reserve(in.size()); for (uint8_t b : in) { switch (b) { case 0xA5: out.push_back(0xA6); out.push_back(0x02); break; case 0xA6: out.push_back(0xA6); out.push_back(0x01); break; case 0x5A: out.push_back(0x5B); out.push_back(0x02); break; case 0x5B: out.push_back(0x5B); out.push_back(0x01); break; default: out.push_back(b); break; } } return out; } } // anonymous namespace uint16_t crc16(const std::vector& data) { uint16_t crc = 0; for (uint8_t b : data) { crc ^= b; for (int i = 0; i < 8; ++i) { crc = (crc & 1u) ? static_cast((crc >> 1) ^ 0xA001u) : static_cast(crc >> 1); } } return crc; } std::vector makeFrame(const std::vector& payload) { std::vector phy1; appendLe16(phy1, 0xFFFE); // 目标:广播/任意控制器 appendLe16(phy1, 0x8000); // 源:PC phy1.push_back(0x00); // barcode 选项:关 phy1.push_back(0x00); phy1.push_back(0x00); phy1.push_back(0x00); // CRC16 模式 phy1.push_back(0x00); // 显示模式(动态区域文本) phy1.push_back(kDeviceType); phy1.push_back(kProtocolVersion); appendLe16(phy1, static_cast(payload.size())); phy1.insert(phy1.end(), payload.begin(), payload.end()); uint16_t cs = crc16(phy1); phy1.push_back(static_cast(cs & 0xff)); phy1.push_back(static_cast((cs >> 8) & 0xff)); std::vector frame; frame.reserve(9 + phy1.size() * 2); frame.insert(frame.end(), 8, 0xA5); // 8 字节头 auto esc = escape(phy1); frame.insert(frame.end(), esc.begin(), esc.end()); frame.push_back(0x5A); // 1 字节尾 return frame; } std::vector buildScreenParameterQueryFrame() { return makeFrame(std::vector{0xA2, 0x0A, 0x01, 0x00, 0x00}); } // 27 字节 AreaHeader + 文本内容(与 Windows 版 buildArea 等价) static std::vector buildArea(const std::string& text, int screenWidth, int areaY, int areaHeight, int areaId, bool expireWhenNotUpdated, int timeoutSeconds) { std::vector area; area.reserve(27 + text.size()); area.push_back(0x00); // AreaType: 字符 appendLe16(area, 0x8000); // AreaX: 0(像素单位) appendLe16(area, static_cast(areaY)); appendLe16(area, static_cast(0x8000 | screenWidth)); appendLe16(area, static_cast(areaHeight)); area.push_back(static_cast(areaId)); area.push_back(0x00); // 行间距 area.push_back(expireWhenNotUpdated ? 0x02 : 0x00); // RunMode int t = (timeoutSeconds < 0) ? 0 : (timeoutSeconds > 0xffff ? 0xffff : timeoutSeconds); appendLe16(area, static_cast(t)); area.push_back(0x00); // SoundMode area.push_back(0x00); area.push_back(0x00); // 保留 area.push_back(0x02); // 多行 area.push_back(0x01); // 手动换行 area.push_back(0x01); // 静态显示 area.push_back(0x00); // 退出方式 area.push_back(0x02); // 速度 area.push_back(0x0A); // 停留(0.5s 单位) appendLe32(area, static_cast(text.size())); area.insert(area.end(), text.begin(), text.end()); return area; } std::vector buildDynamicTextFrame(const std::string& encodedText, int screenWidth, int lineHeight, int areaId, bool expireWhenNotUpdated, int timeoutSeconds) { if (screenWidth <= 0 || screenWidth > 0x7fff) return {}; if (lineHeight <= 0) return {}; const std::vector area = buildArea(encodedText, screenWidth, /*areaY=*/0, lineHeight, areaId, expireWhenNotUpdated, timeoutSeconds); if (area.empty() || area.size() > 0xffff) return {}; std::vector payload; payload.push_back(0xA3); payload.push_back(0x06); // 动态区域下发 payload.push_back(0x01); // 需要 ACK payload.push_back(0x00); // ProcessMode: 替换 payload.push_back(0x00); // 保留 payload.push_back(0x00); // DeleteAreaNum: 0 payload.push_back(0x01); // AreaNum: 1 appendLe16(payload, static_cast(area.size())); payload.insert(payload.end(), area.begin(), area.end()); return makeFrame(payload); } std::vector buildDeleteDynamicAreaFrame(int areaId) { if (areaId < 0 || areaId > 0xff) return {}; std::vector payload = { 0xA3, 0x07, 0x01, 0x00, 0x00, 0x01, static_cast(areaId) }; return makeFrame(payload); } bool decodeFrame(const std::vector& frame, std::vector& outPhy1, std::string& error) { outPhy1.clear(); if (frame.size() < 8 + 14 + 2 + 1 || frame.back() != 0x5A) { error = "frame too short or missing tail"; return false; } for (size_t i = 0; i < 8; ++i) { if (frame[i] != 0xA5) { error = "header invalid"; return false; } } std::vector body(frame.begin() + 8, frame.end() - 1); for (size_t i = 0; i < body.size(); ++i) { uint8_t b = body[i]; if (b == 0xA6 || b == 0x5B) { if (++i >= body.size()) { error = "truncated escape"; return false; } uint8_t n = body[i]; if (b == 0xA6) { if (n == 0x02) outPhy1.push_back(0xA5); else if (n == 0x01) outPhy1.push_back(0xA6); else { error = "bad A6 escape"; return false; } } else { if (n == 0x02) outPhy1.push_back(0x5A); else if (n == 0x01) outPhy1.push_back(0x5B); else { error = "bad 5B escape"; return false; } } } else { outPhy1.push_back(b); } } if (outPhy1.size() < 16) { error = "phy1 too short"; return false; } std::vector withoutCrc(outPhy1.begin(), outPhy1.end() - 2); uint16_t expected = crc16(withoutCrc); uint16_t actual = static_cast(outPhy1[outPhy1.size() - 2]) | static_cast(outPhy1[outPhy1.size() - 1] << 8); if (expected != actual) { error = "crc16 mismatch"; return false; } outPhy1.pop_back(); outPhy1.pop_back(); return true; } bool parseScreenDimensions(const std::vector& phy1, int& outWidth, int& outHeight, std::string& error) { if (phy1.size() < 14 + 13 || phy1[10] != kDeviceType || phy1[11] != kProtocolVersion) { error = "response header invalid"; return false; } std::vector payload(phy1.begin() + 14, phy1.end()); if (payload.size() < 13 || payload[0] != 0xA2 || payload[1] != 0x0A || payload[2] != 0x00) { error = "response payload invalid"; return false; } outWidth = static_cast(payload[9]) | (static_cast(payload[10]) << 8); outHeight = static_cast(payload[11]) | (static_cast(payload[12]) << 8); if (outWidth == 0 || outHeight == 0) { error = "invalid dimensions"; return false; } return true; } } // namespace led