You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
3.7 KiB
135 lines
3.7 KiB
import { create, toBinary } from "@bufbuild/protobuf";
|
|
import * as Protobuf from "@meshtastic/protobufs";
|
|
import crc16ccitt from "crc/calculators/crc16ccitt";
|
|
|
|
//if counter > 35 then reset counter/clear/error/reject promise
|
|
type XmodemProps = (toRadio: Uint8Array, id?: number) => Promise<number>;
|
|
|
|
export class Xmodem {
|
|
private sendRaw: XmodemProps;
|
|
private rxBuffer: Uint8Array[];
|
|
private txBuffer: Uint8Array[];
|
|
private textEncoder: TextEncoder;
|
|
private counter: number;
|
|
|
|
constructor(sendRaw: XmodemProps) {
|
|
this.sendRaw = sendRaw;
|
|
this.rxBuffer = [];
|
|
this.txBuffer = [];
|
|
this.textEncoder = new TextEncoder();
|
|
this.counter = 0;
|
|
}
|
|
|
|
async downloadFile(filename: string): Promise<number> {
|
|
return await this.sendCommand(
|
|
Protobuf.Xmodem.XModem_Control.STX,
|
|
this.textEncoder.encode(filename),
|
|
0,
|
|
);
|
|
}
|
|
|
|
async uploadFile(filename: string, data: Uint8Array): Promise<number> {
|
|
for (let i = 0; i < data.length; i += 128) {
|
|
this.txBuffer.push(data.slice(i, i + 128));
|
|
}
|
|
|
|
return await this.sendCommand(
|
|
Protobuf.Xmodem.XModem_Control.SOH,
|
|
this.textEncoder.encode(filename),
|
|
0,
|
|
);
|
|
}
|
|
|
|
async sendCommand(
|
|
command: Protobuf.Xmodem.XModem_Control,
|
|
buffer?: Uint8Array,
|
|
sequence?: number,
|
|
crc16?: number,
|
|
): Promise<number> {
|
|
const toRadio = create(Protobuf.Mesh.ToRadioSchema, {
|
|
payloadVariant: {
|
|
case: "xmodemPacket",
|
|
value: {
|
|
buffer,
|
|
control: command,
|
|
seq: sequence,
|
|
crc16: crc16,
|
|
},
|
|
},
|
|
});
|
|
return await this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio));
|
|
}
|
|
|
|
async handlePacket(packet: Protobuf.Xmodem.XModem): Promise<number> {
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
switch (packet.control) {
|
|
case Protobuf.Xmodem.XModem_Control.NUL: {
|
|
// nothing
|
|
break;
|
|
}
|
|
case Protobuf.Xmodem.XModem_Control.SOH: {
|
|
this.counter = packet.seq;
|
|
if (this.validateCrc16(packet)) {
|
|
this.rxBuffer[this.counter] = packet.buffer;
|
|
return this.sendCommand(Protobuf.Xmodem.XModem_Control.ACK);
|
|
}
|
|
return await this.sendCommand(
|
|
Protobuf.Xmodem.XModem_Control.NAK,
|
|
undefined,
|
|
packet.seq,
|
|
);
|
|
}
|
|
case Protobuf.Xmodem.XModem_Control.STX: {
|
|
break;
|
|
}
|
|
case Protobuf.Xmodem.XModem_Control.EOT: {
|
|
// end of transmission
|
|
break;
|
|
}
|
|
case Protobuf.Xmodem.XModem_Control.ACK: {
|
|
this.counter++;
|
|
if (this.txBuffer[this.counter - 1]) {
|
|
return this.sendCommand(
|
|
Protobuf.Xmodem.XModem_Control.SOH,
|
|
this.txBuffer[this.counter - 1],
|
|
this.counter,
|
|
crc16ccitt(this.txBuffer[this.counter - 1] ?? new Uint8Array()),
|
|
);
|
|
}
|
|
if (this.counter === this.txBuffer.length + 1) {
|
|
return this.sendCommand(Protobuf.Xmodem.XModem_Control.EOT);
|
|
}
|
|
this.clear();
|
|
break;
|
|
}
|
|
case Protobuf.Xmodem.XModem_Control.NAK: {
|
|
return this.sendCommand(
|
|
Protobuf.Xmodem.XModem_Control.SOH,
|
|
this.txBuffer[this.counter],
|
|
this.counter,
|
|
crc16ccitt(this.txBuffer[this.counter - 1] ?? new Uint8Array()),
|
|
);
|
|
}
|
|
case Protobuf.Xmodem.XModem_Control.CAN: {
|
|
this.clear();
|
|
break;
|
|
}
|
|
case Protobuf.Xmodem.XModem_Control.CTRLZ: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Promise.resolve(0);
|
|
}
|
|
|
|
validateCrc16(packet: Protobuf.Xmodem.XModem): boolean {
|
|
return crc16ccitt(packet.buffer) === packet.crc16;
|
|
}
|
|
|
|
clear() {
|
|
this.counter = 0;
|
|
this.rxBuffer = [];
|
|
this.txBuffer = [];
|
|
}
|
|
}
|
|
|