|
|
// 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
|