Ошибки при передаче данных по 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, пк принимает, захватывает новый цвет и отсылает на плату, плата до момента получения данных весит в бесконечном цикле ожидая пакет, как только данные пришли, они записываются в массив цветов, ну а дальше идет отрисовка на ленте и все повторяется.
Характер ошибок следующий:
- Периодически возникает ошибка typeerror: failed to execute 'getwriter' on 'writablestream': cannot create writer when writablestream is locked. Пробовал дождаться открытия через writeable.locked, но сильно увеличивается количество ошибок на ленте.
- Ошибки на ленте, связаны с приходом не корректных данных, возникают не очень часто, но вызывают неприятное моргание ленты. Эти ошибки в принципе можно исправить введением контрольной суммы в пакете, но в любом случае остается главная проблема - ошибки типа 3.
- Прекращение обмена данными, порт остается открытым, но данные не пересылаются. При попытке перезапустить порт и отправить новые данные возникает большое кол-во ошибок первого типа, после чего обмен прерывается вновь.
Важно отметить, что ошибки каждого типа имеют свойство появляться в моменты, когда пк выполняет какой-либо затратный по ресурсам процесс. Например система спокойно работает при воспроизведении видео скажем в 480p-720p (даже фильмы с ней смотрел), но на fullHD уже отваливается. Игры или иной тяжелый софт вызывают прекращение обмена данными практически сразу.
Вся выше перечисленная информация вполне может быть полным бредом, т.к. я все-таки не разбираюсь в этой области, поэтому прошу помощи знающих людей.