feat(device-plane): add typed B2 service ping transport

This commit is contained in:
Codex
2026-08-12 22:57:09 +03:00
parent bdb85cb4f7
commit 4b73a15765
24 changed files with 1322 additions and 88 deletions
@@ -51,8 +51,8 @@ export const ARUSNAVI_B2_MODEL_PROFILE = deepFreeze({
package: "package-number-only",
},
commandTransport: {
status: "disabled",
exportedCommandBuilders: 0,
status: "typed-service-ping-v1",
exportedCommandBuilders: 1,
},
routeCompatibility: {
gelios: "parallel-preserved",
@@ -76,6 +76,8 @@ export const ARUSNAVI_B2_ADAPTER = defineDeviceAdapter({
buildMessageAcknowledgement(message) {
return buildB2PackageAcknowledgement(message.packageNumber);
},
buildTypedCommand: buildB2TypedCommand,
parseTypedCommandResponse: tryParseB2TypedCommandResponse,
};
},
});
@@ -238,6 +240,36 @@ export function buildB2PackageAcknowledgement(packageNumber) {
]);
}
export function buildB2TypedCommand({ commandType, accessCode } = {}) {
if (commandType !== "service.ping") {
throw new TypeError("b2_typed_command_unsupported");
}
if (typeof accessCode !== "string" || !/^\d{6}$/.test(accessCode)) {
throw new TypeError("b2_command_access_code_invalid");
}
return Buffer.from(`${accessCode}*SERV*1.1`, "ascii");
}
export function tryParseB2TypedCommandResponse(input, { commandType } = {}) {
assertBuffer(input, "b2_command_response_buffer_required");
if (commandType !== "service.ping") {
throw new TypeError("b2_typed_command_unsupported");
}
const expected = Buffer.from("SERV OK", "ascii");
const compared = Math.min(input.length, expected.length);
if (!input.subarray(0, compared).equals(expected.subarray(0, compared))) {
return Object.freeze({ status: "not-command" });
}
if (input.length < expected.length) {
return Object.freeze({ status: "incomplete", minimumBytes: expected.length });
}
return Object.freeze({
status: "acknowledged",
bytesConsumed: expected.length,
resultCode: "serv_ok",
});
}
export function assertB2ProfileInvariant(profile = ARUSNAVI_B2_MODEL_PROFILE) {
if (profile.monitoringServerSlots !== 4) {
throw new TypeError("b2_server_slot_count_invalid");
@@ -257,8 +289,8 @@ export function assertB2ProfileInvariant(profile = ARUSNAVI_B2_MODEL_PROFILE) {
) {
throw new TypeError("b2_framing_specification_invalid");
}
if (profile.commandTransport.status !== "disabled") {
throw new TypeError("b2_command_transport_must_be_disabled");
if (profile.commandTransport.status !== "typed-service-ping-v1") {
throw new TypeError("b2_command_transport_profile_invalid");
}
if (profile.routeCompatibility.gelios !== "parallel-preserved") {
throw new TypeError("b2_gelios_route_must_be_preserved");
@@ -7,8 +7,10 @@ import {
assertB2ProfileInvariant,
buildB2HeaderAcknowledgement,
buildB2PackageAcknowledgement,
buildB2TypedCommand,
tryParseB2Header2,
tryParseB2Package,
tryParseB2TypedCommandResponse,
} from "../src/index.mjs";
const specificationHeader = Buffer.from(
@@ -131,10 +133,32 @@ test("fails closed on unsupported headers and malformed packages", () => {
);
});
test("exports no command builder and keeps transport disabled", () => {
assert.equal(ARUSNAVI_B2_MODEL_PROFILE.commandTransport.status, "disabled");
test("exports only the typed service-ping command", () => {
assert.equal(
ARUSNAVI_B2_MODEL_PROFILE.commandTransport.status,
"typed-service-ping-v1",
);
assert.equal(
ARUSNAVI_B2_MODEL_PROFILE.commandTransport.exportedCommandBuilders,
0,
1,
);
assert.equal(
buildB2TypedCommand({ commandType: "service.ping", accessCode: "123456" })
.toString("ascii"),
"123456*SERV*1.1",
);
assert.deepEqual(
tryParseB2TypedCommandResponse(Buffer.from("SERV OK", "ascii"), {
commandType: "service.ping",
}),
{ status: "acknowledged", bytesConsumed: 7, resultCode: "serv_ok" },
);
assert.throws(
() => buildB2TypedCommand({ commandType: "service.ping", accessCode: "12345" }),
/b2_command_access_code_invalid/,
);
assert.throws(
() => buildB2TypedCommand({ commandType: "firmware.update", accessCode: "123456" }),
/b2_typed_command_unsupported/,
);
});
@@ -21,6 +21,7 @@ export const EDGE_TO_CORE_MESSAGE_KINDS = Object.freeze([
"discovery.observed",
"adapter.message",
"delivery.acknowledged",
"command.status",
"channel.counters",
]);