Added KISS support
This commit is contained in:
79
src/kiss.ts
Normal file
79
src/kiss.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// KISS (Keep It Simple Stupid) frame reader/writer for AX.25
|
||||||
|
// See: http://www.ax25.net/kiss.aspx
|
||||||
|
|
||||||
|
export const KISS_FEND = 0xc0;
|
||||||
|
export const KISS_FESC = 0xdb;
|
||||||
|
export const KISS_TFEND = 0xdc;
|
||||||
|
export const KISS_TFESC = 0xdd;
|
||||||
|
|
||||||
|
export type KissFrame = {
|
||||||
|
port: number; // 0-15
|
||||||
|
command: number; // 0 = data, 1 = TX delay, etc.
|
||||||
|
data: Uint8Array;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Encode a KISS frame (data only, port 0 by default)
|
||||||
|
export function encodeKissFrame(data: Uint8Array, port = 0, command = 0): Uint8Array {
|
||||||
|
const out: number[] = [KISS_FEND];
|
||||||
|
// Command byte: upper 4 bits = port, lower 4 bits = command
|
||||||
|
out.push(((port & 0x0f) << 4) | (command & 0x0f));
|
||||||
|
for (const b of data) {
|
||||||
|
if (b === KISS_FEND) {
|
||||||
|
out.push(KISS_FESC, KISS_TFEND);
|
||||||
|
} else if (b === KISS_FESC) {
|
||||||
|
out.push(KISS_FESC, KISS_TFESC);
|
||||||
|
} else {
|
||||||
|
out.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.push(KISS_FEND);
|
||||||
|
return Uint8Array.from(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a KISS frame (returns null if not a valid frame)
|
||||||
|
export function decodeKissFrame(buf: Uint8Array): KissFrame | null {
|
||||||
|
if (buf.length < 3 || buf[0] !== KISS_FEND || buf[buf.length - 1] !== KISS_FEND) return null;
|
||||||
|
let i = 1;
|
||||||
|
const cmd = buf[i++];
|
||||||
|
const port = (cmd >> 4) & 0x0f;
|
||||||
|
const command = cmd & 0x0f;
|
||||||
|
const data: number[] = [];
|
||||||
|
while (i < buf.length - 1) {
|
||||||
|
const b = buf[i++];
|
||||||
|
if (b === KISS_FESC) {
|
||||||
|
const next = buf[i++];
|
||||||
|
if (next === KISS_TFEND) data.push(KISS_FEND);
|
||||||
|
else if (next === KISS_TFESC) data.push(KISS_FESC);
|
||||||
|
else return null; // Invalid escape
|
||||||
|
} else {
|
||||||
|
data.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { port, command, data: Uint8Array.from(data) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async generator to extract KISS frames from a stream of bytes
|
||||||
|
export async function* kissFrameReader(source: AsyncIterable<Uint8Array>): AsyncGenerator<KissFrame> {
|
||||||
|
let buffer: number[] = [];
|
||||||
|
let inFrame = false;
|
||||||
|
for await (const chunk of source) {
|
||||||
|
for (const b of chunk) {
|
||||||
|
if (b === KISS_FEND) {
|
||||||
|
if (inFrame && buffer.length > 0) {
|
||||||
|
const frame = decodeKissFrame(Uint8Array.from([KISS_FEND, ...buffer, KISS_FEND]));
|
||||||
|
if (frame) yield frame;
|
||||||
|
}
|
||||||
|
buffer = [];
|
||||||
|
inFrame = true;
|
||||||
|
} else if (inFrame) {
|
||||||
|
buffer.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a KISS frame to a sink (e.g., serial port)
|
||||||
|
export async function kissFrameWriter(sink: (data: Uint8Array) => Promise<void>, frame: KissFrame) {
|
||||||
|
const buf = encodeKissFrame(frame.data, frame.port, frame.command);
|
||||||
|
await sink(buf);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user