import { create, fromBinary, toBinary } from '@bufbuild/protobuf'; let proto: typeof import('@meshtastic/protobufs') | null = null; async function loadProto(): Promise { if (!proto) proto = await import('@meshtastic/protobufs'); return proto!; } export const BROADCAST_ADDR = 0xffffffff; // official portnums.proto: TEXT_MESSAGE_APP=1 (task spec draft said 3 — the enum value wins) export const PORT_TEXT_MESSAGE_APP = 1; // verified against installed @meshtastic/protobufs Portnums.PortNum.POSITION_APP export const PORT_POSITION_APP = 3; // verified against installed @meshtastic/protobufs Portnums.PortNum.ROUTING_APP export const PORT_ROUTING_APP = 5; const START1 = 0x94; const START2 = 0xc3; const MAX_LEN = 1024; export function extractFrames(buf: Uint8Array): { packets: Uint8Array[]; rest: Uint8Array; } { const packets: Uint8Array[] = []; let i = 0; for (;;) { if (i >= buf.length) return { packets, rest: new Uint8Array(0) }; const isHeaderStart = buf[i] === START1 && i + 1 < buf.length && buf[i + 1] === START2; if (!isHeaderStart) { // keep a trailing lone 0x94 — it may be the first byte of a split header if (buf[i] === START1 && i + 1 >= buf.length) return { packets, rest: buf.subarray(i) }; i++; continue; } if (i + 4 > buf.length) return { packets, rest: buf.subarray(i) }; // partial header const len = (buf[i + 2] << 8) | buf[i + 3]; if (len > MAX_LEN) { i++; // bogus header — rescan from next byte continue; } if (i + 4 + len > buf.length) return { packets, rest: buf.subarray(i) }; // partial payload packets.push(buf.subarray(i + 4, i + 4 + len)); i += 4 + len; } } export function frame(payload: Uint8Array): Uint8Array { const out = new Uint8Array(4 + payload.length); out[0] = START1; out[1] = START2; out[2] = (payload.length >> 8) & 0xff; out[3] = payload.length & 0xff; out.set(payload, 4); return out; } export async function decodeFromRadio( bytes: Uint8Array, ): Promise { const P = await loadProto(); return fromBinary(P.Mesh.FromRadioSchema, bytes); } /** POSITION_APP packets carry a serialized meshtastic.Position in Data.payload. */ export async function decodePosition( bytes: Uint8Array, ): Promise { const P = await loadProto(); return fromBinary(P.Mesh.PositionSchema, bytes); } /** ROUTING_APP packets carry a serialized meshtastic.Routing in Data.payload (ACK/NACK for want_ack sends). */ export async function decodeRouting( bytes: Uint8Array, ): Promise { const P = await loadProto(); return fromBinary(P.Mesh.RoutingSchema, bytes); } /** Human-readable name for a Routing.Error code (from the installed @meshtastic/protobufs enum), `reason ` fallback. */ export async function describeRoutingError(code: number): Promise { const P = await loadProto(); const hit = Object.entries(P.Mesh.Routing_Error).find( ([, v]) => typeof v === 'number' && v === code, ); return hit ? String(hit[0]) : `reason ${code}`; } let hwModelNames: Map | null = null; /** Reverse lookup of meshtastic.HardwareModel (enum value -> name), loaded once. */ export async function loadHwModelNames(): Promise> { if (!hwModelNames) { const P = await loadProto(); hwModelNames = new Map( Object.entries(P.Mesh.HardwareModel).map(([k, v]) => [ Number(k), String(v), ]), ); } return hwModelNames; } export async function buildTextToRadio( to: number, text: string, channelIndex: number, ): Promise<{ bytes: Uint8Array; id: number }> { const P = await loadProto(); let id = Math.floor(Math.random() * 0x100000000) >>> 0; if (id === 0) id = 1; // non-zero uint32 const meshPacket = create(P.Mesh.MeshPacketSchema, { to, id, wantAck: true, channel: channelIndex, payloadVariant: { case: 'decoded', value: { portnum: P.Portnums.PortNum.TEXT_MESSAGE_APP, payload: new TextEncoder().encode(text), wantResponse: true, }, }, }); const toRadio = create(P.Mesh.ToRadioSchema, { payloadVariant: { case: 'packet', value: meshPacket }, }); return { bytes: frame(toBinary(P.Mesh.ToRadioSchema, toRadio)), id }; } export async function buildWantConfigToRadio( wantConfigId: number, ): Promise { const P = await loadProto(); const toRadio = create(P.Mesh.ToRadioSchema, { payloadVariant: { case: 'wantConfigId', value: wantConfigId }, }); return toBinary(P.Mesh.ToRadioSchema, toRadio); }