summaryrefslogtreecommitdiffstats
path: root/assets/js/xymini.js
diff options
context:
space:
mode:
Diffstat (limited to 'assets/js/xymini.js')
-rw-r--r--assets/js/xymini.js147
1 files changed, 147 insertions, 0 deletions
diff --git a/assets/js/xymini.js b/assets/js/xymini.js
new file mode 100644
index 0000000..a1fd1ca
--- /dev/null
+++ b/assets/js/xymini.js
@@ -0,0 +1,147 @@
+/* XYMini Sender - Minimal implementation of file transfer through serial
+ * Copyright (C) Ernesto Castellotti <mail@ernestocastellotti.it>
+ * SPDX-License-Identifier: MPL-2.0-no-copyleft-exception
+ *
+ * Warning: This does not comply with XMODEM and YMODEM standards
+*/
+
+const STX = 0x02;
+const ACK = 0x06;
+const NAK = 0x15;
+const EOF = 0x04;
+const XYMINI_1K_MAGIC = 0x43;
+const PAYLOAD_LEN = 1024;
+const BLOCK_LEN = PAYLOAD_LEN + 5;
+const CRC_POLY = 0x1021;
+
+function uint16 (n) {
+ return n & 0xFFFF;
+}
+
+function updateCrc(crcIn, incr) {
+ const xor = uint16(crcIn >> 15);
+ let result = uint16(crcIn << 1);
+
+ if (incr) {
+ result = uint16(result + 1);
+ }
+
+ if (xor) {
+ result = uint16(result ^= CRC_POLY);
+ }
+
+ return result;
+}
+
+function crc16(data) {
+ let crc;
+
+ for (let i = 0; i < data.length; i++) {
+ for (let j = 0x80; j; j >>= 1) {
+ crc = updateCrc(crc, data[i] & j);
+ }
+ }
+
+ for (let n = 0; n < 16; n++) {
+ crc = updateCrc(crc, 0);
+ }
+
+ return crc;
+}
+
+async function detectXYMini(reader) {
+ const textDecoder = new TextDecoder();
+
+ while (true) {
+ const { value, done } = await reader.read();
+
+ if (value[0] == XYMINI_1K_MAGIC) {
+ console.log("XYMini: detected");
+ break;
+ }
+ }
+}
+
+function generateXYMiniBlock(blockId, payload) {
+ let buf = new Uint8Array(BLOCK_LEN);
+ let i = 0;
+
+ buf[i++] = STX;
+ buf[i++] = blockId;
+ buf[i++] = 0xFF - blockId;
+
+ if (payload.length > PAYLOAD_LEN) {
+ throw new Error("Payload too large to be transmitted in one block");
+ }
+
+ for (let j = 0; j < payload.length; j++) {
+ buf[i++] = payload[j];
+ }
+
+ while (i < BLOCK_LEN - 2) {
+ buf[i++] = 0xFF;
+ }
+
+ let crcBuf = buf.slice(3, PAYLOAD_LEN + 3)
+ let crc = crc16(crcBuf);
+
+ buf[i++] = (crc >> 8) & 0xFF;
+ buf[i++] = crc & 0xFF;
+
+ return buf;
+}
+
+async function sendXYMini(portReader, portWriter, data, baudRate = 115200, progressCallback) {
+ let blockId = 1;
+ let size = data.length;
+ let i = 0;
+ let nakN = 0;
+ let wrongCharN = 0;
+
+ await detectXYMini(portReader);
+
+ while(true) {
+ const payloadSize = Math.min(PAYLOAD_LEN, size);
+
+ if (size) {
+ const payload = data.slice(i, payloadSize + i);
+
+ const block = generateXYMiniBlock(blockId, payload);
+ await portWriter.write(block);
+ } else {
+ portWriter.write(new Uint8Array([EOF]));
+ }
+
+ const { value, done } = await portReader.read();
+
+ if (value[0] == ACK) {
+ if (!size) {
+ console.log("XYMini: End of transmission");
+
+ return;
+ }
+
+ blockId++;
+ size -= payloadSize;
+ i += payloadSize;
+ nakN = 0;
+ wrongCharN = 0;
+ progressCallback(data.length - size);
+ } else if (value[0] == NAK) {
+ if (nakN >= 10) {
+ throw new Error("Received 10 NAK, receiver is rejecting file transmission");
+ }
+
+ console.log("XYMini: NAK");
+ nakN++;
+ } else {
+ if (wrongCharN >= 30) {
+ throw new Error("Received 30 wrong characters, the receiver is rejecting the transmission or the connection is too noisy");
+ }
+
+ console.log("XYMini: wrong character");
+ console.log(value);
+ wrongCharN++;
+ }
+ }
+}