feat: retain multi-channel voltage telemetry
This commit is contained in:
1 parent
ad1ff3f03e
commit
131bbafc67
17 files changed
+1603
-138
No files matched your search
@@ -22,6 +22,44 @@ const TEMPERATURE_PACKET =
|
||||
"3E00545442311122334455667788800092651E0008000102354A4E5082";
|
||||
const VOLTAGE_PACKET =
|
||||
"3E00545642311122334455667788800092651E000800010264C8FEFFDC";
|
||||
const EXTERNAL_VOLTAGE_PACKET =
|
||||
"3E00495642311122334455667788800092651E00020800000004019026927109C427107FFF";
|
||||
|
||||
function pack15(codes) {
|
||||
const output = Buffer.alloc(Math.ceil((codes.length * 15) / 8));
|
||||
let bitOffset = 0;
|
||||
codes.forEach((code) => {
|
||||
for (let bit = 14; bit >= 0; bit -= 1, bitOffset += 1) {
|
||||
if (code & (1 << bit)) {
|
||||
output[Math.floor(bitOffset / 8)] |= 1 << (7 - (bitOffset % 8));
|
||||
}
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
function externalPage(epoch, channel, codes) {
|
||||
const header = Buffer.alloc(8);
|
||||
header[0] = 0x14;
|
||||
header.writeUInt32LE(epoch, 1);
|
||||
header[5] = 30;
|
||||
header[6] = codes.length;
|
||||
header[7] = channel;
|
||||
return `> ${Buffer.concat([header, pack15(codes)]).toString("base64")}`;
|
||||
}
|
||||
|
||||
function externalPacket(epoch, channel, codes) {
|
||||
const header = Buffer.alloc(20);
|
||||
header.write("IVB1", 0, "ascii");
|
||||
Buffer.from("1122334455667788", "hex").copy(header, 4);
|
||||
header.writeUInt32LE(epoch, 12);
|
||||
header.writeUInt16LE(30, 16);
|
||||
header[18] = channel;
|
||||
header[19] = codes.length;
|
||||
return Buffer.concat([
|
||||
Buffer.from("3e00", "hex"), header, pack15(codes),
|
||||
]).toString("hex").toUpperCase();
|
||||
}
|
||||
|
||||
test("decodes complete analyzer temperature packet hex", () => {
|
||||
const decoded = decoder.decodeRawTelemetryHex(TEMPERATURE_PACKET);
|
||||
@@ -68,6 +106,66 @@ test("decodes analyzer voltage packet and exact voltage codes", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("decodes full-range I2C voltage packets", () => {
|
||||
const decoded = decoder.decodeTelemetry(EXTERNAL_VOLTAGE_PACKET);
|
||||
assert.strictEqual(decoded.kind, "external-voltage");
|
||||
assert.strictEqual(decoded.typeCode, "IVB1");
|
||||
assert.strictEqual(decoded.channel, 2);
|
||||
assert.strictEqual(decoded.count, 8);
|
||||
assert.deepStrictEqual(
|
||||
decoded.rows.map((row) => [row.status, row.millivolts]),
|
||||
[
|
||||
["Missing", null],
|
||||
["Value", 20],
|
||||
["Value", 1000],
|
||||
["Value", 12340],
|
||||
["Value", 100000],
|
||||
["Value", 200000],
|
||||
["Value", 400000],
|
||||
["Value", 655340],
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test("decodes and merges four I2C voltage CLI pages", () => {
|
||||
const firstEpoch = 1704067200;
|
||||
const pages = [];
|
||||
for (let page = 3; page >= 0; page -= 1) {
|
||||
const codes = Array.from(
|
||||
{ length: 48 }, (_, index) => page * 48 + index + 1
|
||||
);
|
||||
pages.push(externalPage(firstEpoch + page * 48 * 1800, 7, codes));
|
||||
}
|
||||
const decoded = decoder.decodeTelemetry(pages.join("\n"));
|
||||
assert.strictEqual(decoded.kind, "external-voltage");
|
||||
assert.strictEqual(decoded.channel, 7);
|
||||
assert.strictEqual(decoded.count, 192);
|
||||
assert.strictEqual(decoded.pageCount, 4);
|
||||
assert.strictEqual(decoded.rows[0].millivolts, 20);
|
||||
assert.strictEqual(decoded.rows[191].millivolts, 3840);
|
||||
const csv = decoder.modelToCsv(decoder.tableModel(decoded, false));
|
||||
assert.strictEqual(csv.split("\r\n").length, 193);
|
||||
});
|
||||
|
||||
test("merges sequential IVB1 chunks but rejects mixed channels", () => {
|
||||
const firstEpoch = 1704067200;
|
||||
const first = externalPacket(firstEpoch, 2, [1, 2, 3]);
|
||||
const second = externalPacket(firstEpoch + 3 * 1800, 2, [4, 5, 6]);
|
||||
const decoded = decoder.decodeTelemetry(`${second}\n${first}`);
|
||||
assert.strictEqual(decoded.count, 6);
|
||||
assert.strictEqual(decoded.pageCount, 2);
|
||||
assert.deepStrictEqual(
|
||||
decoded.rows.map((row) => row.millivolts),
|
||||
[20, 40, 60, 80, 100, 120]
|
||||
);
|
||||
assertDecoderError(
|
||||
() => decoder.decodeTelemetry(
|
||||
`${first}\n${externalPacket(firstEpoch + 3 * 1800, 3, [4, 5, 6])}`
|
||||
),
|
||||
/same type, source, channel, and interval/
|
||||
);
|
||||
});
|
||||
|
||||
test("accepts payload-only spaced hex", () => {
|
||||
const payload = TEMPERATURE_PACKET.slice(4).match(/.{2}/g).join(" ");
|
||||
const decoded = decoder.decodeRawTelemetryHex(payload);
|
||||
@@ -101,7 +199,7 @@ test("retains legacy CLI Base64 decoding", () => {
|
||||
test("rejects unrelated, truncated, and ambiguous hex", () => {
|
||||
assertDecoderError(
|
||||
() => decoder.decodeRawTelemetryHex("00000000000000000000000000000000000000"),
|
||||
/does not contain TTB1 temperature or TVB1 voltage/
|
||||
/does not contain TTB1, TVB1, or IVB1/
|
||||
);
|
||||
assertDecoderError(
|
||||
() => decoder.decodeRawTelemetryHex(TEMPERATURE_PACKET.slice(0, -2)),
|
||||
|
||||
Reference in new issue
Block a user