Ошибки при передаче данных по Serial JS

Делаю систему динамической подсветки монитора. Есть Arduino Uno, которая по serial принимает с пк управляющий сигнал и выводит цвета на адресную светодиодную ленту (наклеена вокруг монитора), и js который захватывает экран и отправляет крайние цвета экрана на плату (если кому надо, вот ссылка на все файлы кода: https://disk.yandex.ru/d/QjSUSO936er5zA).

Проблема как раз и состоит в появлении ошибок передачи данных. Вот мой код: Главный скрипт:

// константы
const frameRate = 20;
const connectionTimeout = 2000;
const baudRate = 115200;

const ledInWidth = 30;
const ledInHeight = 18;

// поиск элементов
const startCaptureBtn = document.getElementById("start_btn");
const connectBtn = document.getElementById("connect_btn");

const video = document.createElement("video");
video.setAttribute("autoplay", "autoplay");

// подключение к ардуино
const serial = new Serial("\n", baudRate);

connectBtn.addEventListener("click", () => {
    if (!serial.connected && capture.isCapture) serial.connect();
});

// отправка цвета
const colorUpdater = new ColorUpdater(ledInWidth, ledInHeight);
let crashCount = 0;

const sendColor = async function () {
    if (!serial.connected) return;

    let colors = colorUpdater.getColors(video);
    let pack = colors.join(",") + ";";

    //console.log("pack: ", pack);

    try {
        serial.send(pack);
    } catch (err) {
        crashCount += 1;
    }
};

serial.on("connected", () => {
    setTimeout(() => serial.send("start;"), connectionTimeout);
});

serial.on("data", data => {
    data = data.trim();
    //console.log("Arduino: ", data);

    //if (data[0] !== "/") return console.log("Arduino: ", data);

    if (data === "/got_data") {
        sendColor();
        return;
    }
});

// включение записи
const capture = new ScreenCapture();

startCaptureBtn.addEventListener("click", e => {
    e.target.classList.toggle("active");
    if (!capture.isCapture) capture.start();
});

capture.on("launch", stream => {
    video.srcObject = stream;

    const videoTrack = video.srcObject.getVideoTracks()[0];
    const params = videoTrack.getSettings();

    const width = params.width;
    const height = params.height;

    colorUpdater.updateSize(width, height);
});

Обертка для работы с Serial:

(() => {

//константы и переменные
//скорость передачи данных
const baudRate = 9600; 
const terminator = "\n";

//класс файла
class Serial extends EventManager {
    constructor (term = terminator, rate = baudRate) {
        super();

        this.rate = rate;
        this.term = term;

        this.port = null;
    };

    //получение данных с порта
    async listen () {
        const textDecoder = new TextDecoderStream();
        const readableStreamClosed = this.readableStreamClosed = this.port.readable.pipeTo(textDecoder.writable);
        let reader = this.reader = textDecoder.readable.getReader();

        let pack = "";
        while (true) {
            const { value, done } = await reader.read();

            pack += value;  

            if (pack.indexOf(this.term) !== -1) {

                const [data, newValue] = pack.split("\n");
                pack = newValue;
                this._emit("data", data);
            }

            if (done) break;
        }

        // console.warn("connection was interrupted");
        // this._emit("disconnected");
        // this.port = null;
    };

    //остановить чтение
    stopReading () {
        return new Promise(async function (resolve) {
            await this.reader.cancel();
            await this.readableStreamClosed.catch(() => {});
            this.reader.releaseLock();

            resolve();
        }.bind(this));
    };

    //подключиться к порту
    async connect () {
        this.port = await navigator.serial.requestPort();
        await this.port.open({ baudRate: this.rate });

        this.listen();
        this._emit("connected");
    };

    //отключиться
    async close () {
        if (!this.port) return;
        if (this.reader) await this.stopReading();
        await this.port.close();
    };

    //перезапустить порт
    async reset () {
        if (!this.port) return;
        if (this.reader) await this.stopReading();

        await this.port.close();
        await this.port.open({ baudRate: this.rate });
        this.listen();

        this._emit("reseted");
    };

    //отправить данные
    async send (value) {
        if (!this.port) return;

        //while (this.port.writable.locked) await new Promise(resolve => setTimeout(resolve, 2));

        const encoder = new TextEncoder();
        const writer = this.port.writable.getWriter();
        await writer.write(encoder.encode(value));
        writer.releaseLock();

        this._emit("sended");
    };

    get busy () {
        return this.port.writable.locked;
    };

    //есть ли соединение
    get connected () {
        return this.port !== null;
    };
};

// экспорт
window.Serial = Serial;

})();

Прошивка на плате:

// подключение ленты
#include <Adafruit_NeoPixel.h>
#define LED_PIN 6
#define len 120

Adafruit_NeoPixel ctx = Adafruit_NeoPixel(len, LED_PIN, NEO_GRB + NEO_KHZ800);  

// константы
#define frameRate 10
#define baudRate 115200

#define ledInWidth 30
#define ledInHeight 18
#define ledCount (ledInWidth + ledInHeight * 2)
#define channelCount (ledCount * 3)
#define ledOffset (len - ledCount)
#define minInputLen (channelCount * 2 - 10)
#define maxInputLen (channelCount * 4 + 10)

unsigned long timeout = 0;
byte colors[channelCount];
boolean pcReady = false;

void setup() {
  for (int i = 0; i < channelCount; i++) colors[i] = 0;

  //инициализация Serial
  Serial.begin(115200);
  Serial.setTimeout(5);

  //инициализация библиотеки ленты
  ctx.begin();
  ctx.show(); 
}

// ловим данные с компа
void getData () {
  Serial.println("/got_data");

  boolean isDone = true;
  do {
    if (Serial.available()) {
      char str[maxInputLen];
      int amount = Serial.readBytesUntil(';', str, maxInputLen);
      str[amount] = '\0';
  
      int count = 0;
      char* offset = str;
  
      while (true) {
        colors[count++] = atoi(offset);
        offset = strchr(offset, ',');
        if (offset) offset++;
        else break;
      }

      isDone = false;
      //Serial.println(str);
    }
  } while (isDone);
};

// обновляем ленту
void frame() {
  for (byte i = 0; i < ledCount; i++) {
    int ledNum = i + ledOffset;
    int colorNum = i * 3;

    ctx.setPixelColor(ledNum, ctx.Color(colors[colorNum], colors[colorNum + 1], colors[colorNum + 2]));
  }

  ctx.show();
}

void loop() {
  if (!pcReady && Serial.available()) {
      char str[maxInputLen];
      int amount = Serial.readBytesUntil(';', str, maxInputLen);  
      str[amount] = '\0';

      pcReady = true;
      Serial.println("board ready");
      timeout = millis();
  }

  static unsigned long timer = 0;
  if (pcReady && millis() - timeout > 1000 && millis() - timer > frameRate) {
    timer = millis();    

    getData();
    frame();  
  }
}

Обмен данными организован следующим образом: ардуино отправляет на пк строку /got_data, пк принимает, захватывает новый цвет и отсылает на плату, плата до момента получения данных весит в бесконечном цикле ожидая пакет, как только данные пришли, они записываются в массив цветов, ну а дальше идет отрисовка на ленте и все повторяется.

Характер ошибок следующий:

  1. Периодически возникает ошибка typeerror: failed to execute 'getwriter' on 'writablestream': cannot create writer when writablestream is locked. Пробовал дождаться открытия через writeable.locked, но сильно увеличивается количество ошибок на ленте.
  2. Ошибки на ленте, связаны с приходом не корректных данных, возникают не очень часто, но вызывают неприятное моргание ленты. Эти ошибки в принципе можно исправить введением контрольной суммы в пакете, но в любом случае остается главная проблема - ошибки типа 3.
  3. Прекращение обмена данными, порт остается открытым, но данные не пересылаются. При попытке перезапустить порт и отправить новые данные возникает большое кол-во ошибок первого типа, после чего обмен прерывается вновь.

Важно отметить, что ошибки каждого типа имеют свойство появляться в моменты, когда пк выполняет какой-либо затратный по ресурсам процесс. Например система спокойно работает при воспроизведении видео скажем в 480p-720p (даже фильмы с ней смотрел), но на fullHD уже отваливается. Игры или иной тяжелый софт вызывают прекращение обмена данными практически сразу.

Вся выше перечисленная информация вполне может быть полным бредом, т.к. я все-таки не разбираюсь в этой области, поэтому прошу помощи знающих людей.


Ответы (0 шт):