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.

210 lines
8.7 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/BX6K1YYProtocol.cxx
#include "BX6K1YYProtocol.hpp"
#include <algorithm>
namespace led {
namespace {
inline void appendLe16(std::vector<uint8_t>& v, uint16_t value) {
v.push_back(static_cast<uint8_t>(value & 0xff));
v.push_back(static_cast<uint8_t>((value >> 8) & 0xff));
}
inline void appendLe32(std::vector<uint8_t>& v, uint32_t value) {
v.push_back(static_cast<uint8_t>(value & 0xff));
v.push_back(static_cast<uint8_t>((value >> 8) & 0xff));
v.push_back(static_cast<uint8_t>((value >> 16) & 0xff));
v.push_back(static_cast<uint8_t>((value >> 24) & 0xff));
}
std::vector<uint8_t> escape(const std::vector<uint8_t>& in) {
std::vector<uint8_t> 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<uint8_t>& data) {
uint16_t crc = 0;
for (uint8_t b : data) {
crc ^= b;
for (int i = 0; i < 8; ++i) {
crc = (crc & 1u) ? static_cast<uint16_t>((crc >> 1) ^ 0xA001u)
: static_cast<uint16_t>(crc >> 1);
}
}
return crc;
}
std::vector<uint8_t> makeFrame(const std::vector<uint8_t>& payload) {
std::vector<uint8_t> 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<uint16_t>(payload.size()));
phy1.insert(phy1.end(), payload.begin(), payload.end());
uint16_t cs = crc16(phy1);
phy1.push_back(static_cast<uint8_t>(cs & 0xff));
phy1.push_back(static_cast<uint8_t>((cs >> 8) & 0xff));
std::vector<uint8_t> 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<uint8_t> buildScreenParameterQueryFrame() {
return makeFrame(std::vector<uint8_t>{0xA2, 0x0A, 0x01, 0x00, 0x00});
}
// 27 字节 AreaHeader + 文本内容(与 Windows 版 buildArea 等价)
static std::vector<uint8_t> buildArea(const std::string& text,
int screenWidth,
int areaY, int areaHeight,
int areaId,
bool expireWhenNotUpdated,
int timeoutSeconds) {
std::vector<uint8_t> area;
area.reserve(27 + text.size());
area.push_back(0x00); // AreaType: 字符
appendLe16(area, 0x8000); // AreaX: 0像素单位
appendLe16(area, static_cast<uint16_t>(areaY));
appendLe16(area, static_cast<uint16_t>(0x8000 | screenWidth));
appendLe16(area, static_cast<uint16_t>(areaHeight));
area.push_back(static_cast<uint8_t>(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<uint16_t>(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<uint32_t>(text.size()));
area.insert(area.end(), text.begin(), text.end());
return area;
}
std::vector<uint8_t> 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<uint8_t> area = buildArea(encodedText, screenWidth,
/*areaY=*/0, lineHeight, areaId,
expireWhenNotUpdated, timeoutSeconds);
if (area.empty() || area.size() > 0xffff) return {};
std::vector<uint8_t> 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<uint16_t>(area.size()));
payload.insert(payload.end(), area.begin(), area.end());
return makeFrame(payload);
}
std::vector<uint8_t> buildDeleteDynamicAreaFrame(int areaId) {
if (areaId < 0 || areaId > 0xff) return {};
std::vector<uint8_t> payload = {
0xA3, 0x07, 0x01, 0x00, 0x00, 0x01,
static_cast<uint8_t>(areaId)
};
return makeFrame(payload);
}
bool decodeFrame(const std::vector<uint8_t>& frame,
std::vector<uint8_t>& 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<uint8_t> 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<uint8_t> withoutCrc(outPhy1.begin(), outPhy1.end() - 2);
uint16_t expected = crc16(withoutCrc);
uint16_t actual = static_cast<uint16_t>(outPhy1[outPhy1.size() - 2]) |
static_cast<uint16_t>(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<uint8_t>& 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<uint8_t> 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<uint16_t>(payload[9]) | (static_cast<uint16_t>(payload[10]) << 8);
outHeight = static_cast<uint16_t>(payload[11]) | (static_cast<uint16_t>(payload[12]) << 8);
if (outWidth == 0 || outHeight == 0) { error = "invalid dimensions"; return false; }
return true;
}
} // namespace led