Čtení dat ze serial portu pomocí Serial API

Tak jsem potřeboval, respektive chtěl vyzkoušet, využít JavaScript Serial API pro komunikaci s Arduinem. Toto API podporují pouze Chrome, Edge a Opera (viz https://developer.mozilla.org/en-US/docs/Web/API/Serial). Jelikož mám Chrome, tak nic nebránilo tomu, abych to zkusil.

Důležité upozornění: Soubor HTML je nutno otevřít buď lokálně nebo ze zabezpečeného HTTPS, jinak je Serial API prohlížečem blokováno!

https://www.ctvrtky.info/wp-content/uploads/2025/05/Serial-API.html

Funguje to moc pěkně, tak zkusím v budoucnu i obousměrnou komunikaci. Například pro nastavení zařízení vypadá taková stránka mnohem lépe, než prostý příkazový řádek. Nehledě na to, že lze pomocí HTML + CSS + JS vytvořit již dostatečně robustní a designově hezkou aplikaci.

Sketch do Arduina

void setup() {
  Serial.begin(9600);
  sendJSON();
}

void loop() {
}

void sendJSON() {
  while (Serial.available() <= 0) {

    String out = "{\"time\":";
    out += millis();
    out += "}";
    
    Serial.println(out);
    delay(1000);
  }
}

HTML + JS

<button hidden>open</button><br>
<textarea cols="40" rows="5" hidden></textarea>

<script>
button = document.querySelector('button');
textOut = document.querySelector('textarea');
textOut.value = "";

// Třída pro ošetření čtení řádků oddělených \n
class LineBreakTransformer {
	constructor() {
		this.chunks = "";
	}
	transform(chunk, controller) {
		this.chunks += chunk;
		const lines = this.chunks.split("\n");
		this.chunks = lines.pop();
		lines.forEach((line) => controller.enqueue(line));
	}
	flush(controller) {
		controller.enqueue(this.chunks);
	}
}

if('serial' in navigator) {
	button.hidden = false;
	button.addEventListener('click', async () => {
		try {
		   	 // Výzva uživatele k vybrání portu
			const port = await navigator.serial.requestPort();
			// Otevření portu
			await port.open({
				baudRate: 9600
			});
			const textDecoder = new TextDecoderStream();
			const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
			const reader = textDecoder.readable.pipeThrough(new TransformStream(new LineBreakTransformer())).getReader();
			textOut.hidden = false;
			// Čekání na příchozí data z portu
			while(true) {
				const {
					value,
					done
				} = await reader.read();
				if(done) {
					reader.releaseLock();
					break;
				}
				// Hodnota je string
				try {
					var o = JSON.parse(value);
					if(o && typeof o === "object") {
					    var d = new Date();
					    textOut.value += d.toLocaleTimeString() + ": " + o.time + '\r\n';
					    textOut.scrollTop = textOut.scrollHeight;
					}
				} catch (e) {}
			}
			const textEncoder = new TextEncoderStream();
			const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
			reader.cancel();
			await readableStreamClosed.catch(() => {
				// Ignorace chyby 
				});
			writer.close();
			await writableStreamClosed;
			await port.close();
		} catch (e) {
			// Uživatel nevybral žádný port
		}
	});
}else{
    textOut.hidden = false;
    textOut.value = "Tento prohlížeč nepodporuje Serial API, nebo je soubor otevřen z nezabezpečené URL!"
}

function tryParseJSONObject(jsonString) {
	try {
		var o = JSON.parse(jsonString);
		if(o && typeof o === "object") {
			return o;
		}
	} catch (e) {}
	return false;
};
</script>

Leave a Reply