1. 소개
최종 업데이트: 2020년 7월 21일
빌드할 항목
이 Codelab에서는 Web Serial API를 사용하여 BBC micro:bit 보드와 상호작용하여 5x5 LED 매트릭스에 이미지를 표시하는 웹페이지를 빌드합니다. Web Serial API와 읽기 가능, 쓰기 가능, 변환 스트림을 사용하여 브라우저를 통해 직렬 기기와 통신하는 방법을 알아봅니다.

학습할 내용
- Web Serial 포트를 열고 닫는 방법
- 읽기 루프를 사용하여 입력 스트림의 데이터를 처리하는 방법
- 쓰기 스트림을 통해 데이터를 전송하는 방법
필요한 항목
- 최신 Espruino 펌웨어가 설치된 BBC micro:bit 보드
- 최신 버전의 Chrome (80 이상)
- HTML, CSS, JavaScript, Chrome DevTools에 대한 지식
이 Codelab에서는 micro:bit가 저렴하고, 몇 가지 입력 (버튼)과 출력 (5x5 LED 디스플레이)을 제공하며, 추가 입력과 출력을 제공할 수 있으므로 micro:bit를 사용하기로 했습니다. micro:bit의 기능에 관한 자세한 내용은 Espruino 사이트의 BBC micro:bit 페이지를 참고하세요.
2. Web Serial API 정보
Web Serial API는 웹사이트가 스크립트를 사용하여 직렬 기기를 읽고 쓸 수 있는 방법을 제공합니다. 이 API를 사용하면 웹사이트가 마이크로컨트롤러, 3D 프린터와 같은 직렬 기기와 통신할 수 있으므로 웹과 실제 세계를 연결할 수 있습니다.
웹 기술을 사용하여 빌드된 제어 소프트웨어의 예는 많습니다. 예를 들면 다음과 같습니다.
경우에 따라 이러한 웹사이트는 사용자가 수동으로 설치한 네이티브 에이전트 애플리케이션을 통해 기기와 통신합니다. 다른 경우에는 Electron과 같은 프레임워크를 통해 패키지된 네이티브 애플리케이션으로 애플리케이션이 제공됩니다. 다른 경우에는 사용자가 컴파일된 애플리케이션을 USB 드라이브로 기기에 복사하는 등의 추가 단계를 실행해야 합니다.
사이트와 제어하는 기기 간의 직접적인 통신을 제공하면 사용자 환경을 개선할 수 있습니다.
3. 설정
코드 가져오기
이 Codelab에 필요한 모든 것을 Glitch 프로젝트에 넣었습니다.
- 새 브라우저 탭을 열고 https://web-serial-codelab-start.glitch.me/로 이동합니다.
- Glitch 리믹스 링크를 클릭하여 시작 프로젝트의 자체 버전을 만듭니다.
- 표시 버튼을 클릭한 다음 새 창에서를 선택하여 코드가 작동하는 모습을 확인합니다.
4. 직렬 연결 열기
Web Serial API 지원 여부 확인
가장 먼저 현재 브라우저에서 Web Serial API가 지원되는지 확인해야 합니다. 이렇게 하려면 serial가 navigator에 있는지 확인합니다.
DOMContentLoaded 이벤트에서 프로젝트에 다음 코드를 추가합니다.
script.js - DOMContentLoaded
// CODELAB: Add feature detection here.
const notSupported = document.getElementById('notSupported');
notSupported.classList.toggle('hidden', 'serial' in navigator);
Web Serial이 지원되는지 확인합니다. 이 경우 이 코드는 Web Serial이 지원되지 않는다는 배너를 숨깁니다.
직접 해 보기
- 페이지를 로드합니다.
- 페이지에 Web Serial이 지원되지 않는다는 빨간색 배너가 표시되지 않는지 확인합니다.
직렬 포트 열기
다음으로 직렬 포트를 열어야 합니다. 다른 최신 API와 마찬가지로 Web Serial API는 비동기식입니다. 이렇게 하면 입력을 기다릴 때 UI가 차단되지 않지만, 직렬 데이터는 언제든지 웹페이지에서 수신될 수 있으며 이를 수신 대기하는 방법이 필요하므로 중요합니다.
컴퓨터에 직렬 기기가 여러 개 있을 수 있으므로 브라우저가 포트를 요청하려고 하면 사용자에게 연결할 기기를 선택하라는 메시지가 표시됩니다.
프로젝트에 다음 코드를 추가합니다.
script.js - connect()
// CODELAB: Add code to request & open port here.
// - Request a port and open a connection.
port = await navigator.serial.requestPort();
// - Wait for the port to open.
await port.open({ baudrate: 9600 });
requestPort 호출은 사용자에게 연결할 기기를 묻습니다. port.open를 호출하면 포트가 열립니다. 또한 직렬 기기와 통신하려는 속도를 제공해야 합니다. BBC micro:bit는 USB-직렬 칩과 기본 프로세서 간에 9600보드 연결을 사용합니다.
연결 버튼도 연결하여 사용자가 클릭하면 connect()를 호출하도록 하겠습니다.
프로젝트에 다음 코드를 추가합니다.
script.js - clickConnect()
// CODELAB: Add connect code here.
await connect();
직접 해 보기
이제 프로젝트에 시작하는 데 필요한 최소한의 요소가 있습니다. 연결 버튼을 클릭하면 연결할 직렬 기기를 선택하라는 메시지가 표시되고 micro:bit에 연결됩니다.
- 페이지를 새로고침합니다.
- 연결 버튼을 클릭합니다.
- 직렬 포트 선택기 대화상자에서 BBC micro:bit 기기를 선택하고 연결을 클릭합니다.
- 탭에 직렬 기기에 연결되었음을 나타내는 아이콘이 표시됩니다.

직렬 포트에서 데이터를 수신하는 입력 스트림 설정
연결이 설정되면 기기에서 데이터를 읽기 위해 입력 스트림과 리더를 설정해야 합니다. 먼저 port.readable를 호출하여 포트에서 읽기 가능한 스트림을 가져옵니다. 기기에서 텍스트가 반환될 것을 알고 있으므로 텍스트 디코더를 통해 파이프합니다. 다음으로 리더를 가져와 읽기 루프를 시작합니다.
프로젝트에 다음 코드를 추가합니다.
script.js - connect()
// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable;
reader = inputStream.getReader();
readLoop();
읽기 루프는 루프에서 실행되고 기본 스레드를 차단하지 않고 콘텐츠를 기다리는 비동기 함수입니다. 새 데이터가 도착하면 리더는 value과 done 불리언이라는 두 가지 속성을 반환합니다. done가 true이면 포트가 닫혔거나 더 이상 수신되는 데이터가 없습니다.
프로젝트에 다음 코드를 추가합니다.
script.js - readLoop()
// CODELAB: Add read loop here.
while (true) {
const { value, done } = await reader.read();
if (value) {
log.textContent += value + '\n';
}
if (done) {
console.log('[readLoop] DONE', done);
reader.releaseLock();
break;
}
}
직접 해 보기
이제 프로젝트가 기기에 연결될 수 있으며 기기에서 수신한 데이터가 로그 요소에 추가됩니다.
- 페이지를 새로고침합니다.
- 연결 버튼을 클릭합니다.
- 직렬 포트 선택기 대화상자에서 BBC micro:bit 기기를 선택하고 연결을 클릭합니다.
- Espruino 로고가 표시됩니다.

직렬 포트로 데이터를 전송하는 출력 스트림 설정
직렬 통신은 일반적으로 양방향입니다. 직렬 포트에서 데이터를 수신하는 것 외에도 포트로 데이터를 전송하고 싶습니다. 입력 스트림과 마찬가지로 출력 스트림을 통해 micro:bit에 텍스트만 전송됩니다.
먼저 텍스트 인코더 스트림을 만들고 스트림을 port.writeable에 파이프합니다.
script.js - connect()
// CODELAB: Add code setup the output stream here.
const encoder = new TextEncoderStream();
outputDone = encoder.readable.pipeTo(port.writable);
outputStream = encoder.writable;
Espruino 펌웨어와 직렬로 연결된 경우 BBC micro:bit 보드는 Node.js 셸에서와 유사한 JavaScript read-eval-print loop (REPL)로 작동합니다. 다음으로 스트림에 데이터를 전송하는 메서드를 제공해야 합니다. 아래 코드는 출력 스트림에서 작성기를 가져온 다음 write를 사용하여 각 줄을 보냅니다. 전송되는 각 줄에는 마이크로비트가 전송된 명령어를 평가하도록 하는 줄바꿈 문자 (\n)가 포함됩니다.
script.js - writeToStream()
// CODELAB: Write to output stream
const writer = outputStream.getWriter();
lines.forEach((line) => {
console.log('[SEND]', line);
writer.write(line + '\n');
});
writer.releaseLock();
시스템을 알려진 상태로 만들고 전송된 문자를 다시 에코하지 않도록 하려면 Ctrl-C를 전송하고 에코를 사용 중지해야 합니다.
script.js - connect()
// CODELAB: Send CTRL-C and turn off echo on REPL
writeToStream('\x03', 'echo(false);');
직접 해 보기
이제 프로젝트에서 micro:bit로 데이터를 보내고 받을 수 있습니다. 명령어를 올바르게 전송할 수 있는지 확인해 보겠습니다.
- 페이지를 새로고침합니다.
- 연결 버튼을 클릭합니다.
- 직렬 포트 선택기 대화상자에서 BBC micro:bit 기기를 선택하고 연결을 클릭합니다.
- Chrome DevTools에서 콘솔 탭을 열고
writeToStream('console.log("yes")');를 입력합니다.
페이지에 다음과 같은 내용이 출력됩니다.

5. LED 매트릭스 제어
매트릭스 그리드 문자열 빌드
micro:bit의 LED 매트릭스를 제어하려면 show()를 호출해야 합니다. 이 메서드는 내장된 5x5 LED 화면에 그래픽을 표시합니다. 바이너리 숫자 또는 문자열을 사용합니다.
체크박스를 반복하여 체크된 항목과 체크되지 않은 항목을 나타내는 1과 0의 배열을 생성합니다. 그런 다음 배열을 반전해야 합니다. 체크박스의 순서가 매트릭스의 LED 순서와 반대이기 때문입니다. 그런 다음 배열을 문자열로 변환하고 micro:bit에 전송할 명령어를 만듭니다.
script.js - sendGrid()
// CODELAB: Generate the grid
const arr = [];
ledCBs.forEach((cb) => {
arr.push(cb.checked === true ? 1 : 0);
});
writeToStream(`show(0b${arr.reverse().join('')})`);
체크박스를 연결하여 매트릭스 업데이트
다음으로 체크박스의 변경사항을 수신 대기하고 변경되면 해당 정보를 micro:bit로 전송해야 합니다. 기능 감지 코드 (// CODELAB: Add feature detection here.)에 다음 줄을 추가합니다.
script.js - DOMContentLoaded
initCheckboxes();
micro:bit가 처음 연결될 때 그리드를 재설정하여 웃는 얼굴이 표시되도록 하겠습니다. drawGrid() 함수가 이미 제공되었습니다. 이 함수는 sendGrid()와 비슷하게 작동합니다. 1과 0으로 이루어진 배열을 가져와서 적절한 체크박스를 선택합니다.
script.js - clickConnect()
// CODELAB: Reset the grid on connect here.
drawGrid(GRID_HAPPY);
sendGrid();
직접 해 보기
이제 페이지에서 micro:bit에 대한 연결을 열면 스마일이 전송됩니다. 체크박스를 클릭하면 LED 매트릭스의 디스플레이가 업데이트됩니다.
- 페이지를 새로고침합니다.
- 연결 버튼을 클릭합니다.
- 직렬 포트 선택기 대화상자에서 BBC micro:bit 기기를 선택하고 연결을 클릭합니다.
- micro:bit LED 매트릭스에 웃는 얼굴이 표시됩니다.
- 체크박스를 변경하여 LED 매트릭스에 다른 패턴을 그립니다.
6. micro:bit 버튼 연결
micro:bit 버튼에 워치 이벤트 추가
micro:bit에는 LED 매트릭스 양쪽에 버튼이 두 개 있습니다. Espruino는 버튼을 누를 때 이벤트/콜백을 전송하는 setWatch 함수를 제공합니다. 두 버튼을 모두 수신해야 하므로 함수를 일반화하고 이벤트 세부정보를 출력하도록 합니다.
script.js - watchButton()
// CODELAB: Hook up the micro:bit buttons to print a string.
const cmd = `
setWatch(function(e) {
print('{"button": "${btnId}", "pressed": ' + e.state + '}');
}, ${btnId}, {repeat:true, debounce:20, edge:"both"});
`;
writeToStream(cmd);
다음으로, 직렬 포트가 기기에 연결될 때마다 두 버튼 (micro:bit 보드에서 BTN1 및 BTN2로 명명됨)을 연결해야 합니다.
script.js - clickConnect()
// CODELAB: Initialize micro:bit buttons.
watchButton('BTN1');
watchButton('BTN2');
직접 해 보기
연결되면 행복한 얼굴이 표시될 뿐만 아니라 micro:bit의 버튼 중 하나를 누르면 어떤 버튼을 눌렀는지 나타내는 텍스트가 페이지에 추가됩니다. 각 문자가 한 줄에 하나씩 표시될 가능성이 높습니다.
- 페이지를 새로고침합니다.
- 연결 버튼을 클릭합니다.
- 직렬 포트 선택기 대화상자에서 BBC micro:bit 기기를 선택하고 연결을 클릭합니다.
- 마이크로비트 LED 매트릭스에 웃는 얼굴이 표시됩니다.
- micro:bit의 버튼을 누르고 누른 버튼의 세부정보가 포함된 새 텍스트가 페이지에 추가되는지 확인합니다.
7. 변환 스트림을 사용하여 수신 데이터 파싱
기본 스트림 처리
micro:bit 버튼 중 하나를 누르면 micro:bit가 스트림을 통해 직렬 포트로 데이터를 전송합니다. 스트림은 매우 유용하지만 한 번에 모든 데이터를 가져오지 않고 임의로 청크될 수 있으므로 어려울 수도 있습니다.
현재 앱은 수신 스트림이 도착하면 readLoop에 출력합니다. 대부분의 경우 각 문자는 자체 행에 있지만 이는 그다지 유용하지 않습니다. 스트림은 개별 행으로 파싱되어야 하며 각 메시지는 자체 행으로 표시되어야 합니다.
TransformStream로 스트림 변환
이를 위해 수신 스트림을 파싱하고 파싱된 데이터를 반환할 수 있는 변환 스트림 ( TransformStream)을 사용할 수 있습니다. 변환 스트림은 스트림 소스 (이 경우 micro:bit)와 스트림을 소비하는 항목 (이 경우 readLoop) 사이에 위치할 수 있으며 최종적으로 소비되기 전에 임의의 변환을 적용할 수 있습니다. 조립 라인과 비슷하다고 생각하면 됩니다. 위젯이 라인을 따라 내려오면 라인의 각 단계에서 위젯을 수정하므로 최종 목적지에 도달할 때쯤에는 완전히 작동하는 위젯이 됩니다.
자세한 내용은 MDN의 Streams API 개념을 참고하세요.
LineBreakTransformer로 스트림 변환
스트림을 가져와 줄바꿈 (\r\n)을 기반으로 청크하는 LineBreakTransformer 클래스를 만들어 보겠습니다. 클래스에는 transform 및 flush의 두 가지 메서드가 필요합니다. transform 메서드는 스트림에서 새 데이터를 수신할 때마다 호출됩니다. 데이터를 대기열에 추가하거나 나중에 사용할 수 있도록 저장할 수 있습니다. flush 메서드는 스트림이 닫힐 때 호출되며 아직 처리되지 않은 데이터를 처리합니다.
transform 메서드에서 container에 새 데이터를 추가한 다음 container에 줄바꿈이 있는지 확인합니다. 있는 경우 배열로 분할한 다음 줄을 반복하여 controller.enqueue()를 호출하여 파싱된 줄을 전송합니다.
script.js - LineBreakTransformer.transform()
// CODELAB: Handle incoming chunk
this.container += chunk;
const lines = this.container.split('\r\n');
this.container = lines.pop();
lines.forEach(line => controller.enqueue(line));
스트림이 닫히면 enqueue를 사용하여 컨테이너에 남아 있는 데이터를 플러시합니다.
script.js - LineBreakTransformer.flush()
// CODELAB: Flush the stream.
controller.enqueue(this.container);
마지막으로 수신 스트림을 새 LineBreakTransformer를 통해 파이프해야 합니다. 원래 입력 스트림은 TextDecoderStream을 통해서만 파이프되었으므로 새 LineBreakTransformer를 통해 파이프하려면 pipeThrough을 추가해야 합니다.
script.js - connect()
// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable
.pipeThrough(new TransformStream(new LineBreakTransformer()));
직접 해 보기
이제 micro:bit 버튼 중 하나를 누르면 인쇄된 데이터가 한 줄로 반환됩니다.
- 페이지를 새로고침합니다.
- 연결 버튼을 클릭합니다.
- 직렬 포트 선택기 대화상자에서 BBC micro:bit 기기를 선택하고 연결을 클릭합니다.
- micro:bit LED 매트릭스에 웃는 얼굴이 표시됩니다.
- micro:bit의 버튼을 누르고 다음과 같은 내용이 표시되는지 확인합니다.

JSONTransformer로 스트림 변환
readLoop에서 문자열을 JSON으로 파싱할 수도 있지만, 대신 데이터를 JSON 객체로 변환하는 매우 간단한 JSON 변환기를 만들어 보겠습니다. 데이터가 유효한 JSON이 아니면 수신된 데이터를 그대로 반환합니다.
script.js - JSONTransformer.transform
// CODELAB: Attempt to parse JSON content
try {
controller.enqueue(JSON.parse(chunk));
} catch (e) {
controller.enqueue(chunk);
}
그런 다음 LineBreakTransformer을 통과한 후 스트림을 JSONTransformer을 통해 파이프합니다. 이렇게 하면 JSON이 항상 단일 행으로만 전송된다는 것을 알기 때문에 JSONTransformer를 단순하게 유지할 수 있습니다.
script.js - connect
// CODELAB: Add code to read the stream here.
let decoder = new TextDecoderStream();
inputDone = port.readable.pipeTo(decoder.writable);
inputStream = decoder.readable
.pipeThrough(new TransformStream(new LineBreakTransformer()))
.pipeThrough(new TransformStream(new JSONTransformer()));
직접 해 보기
이제 micro:bit 버튼 중 하나를 누르면 페이지에 [object Object]가 출력됩니다.
- 페이지를 새로고침합니다.
- 연결 버튼을 클릭합니다.
- 직렬 포트 선택기 대화상자에서 BBC micro:bit 기기를 선택하고 연결을 클릭합니다.
- micro:bit LED 매트릭스에 웃는 얼굴이 표시됩니다.
- micro:bit의 버튼을 누르고 다음과 같이 표시되는지 확인합니다.
버튼 누르기에 응답
micro:bit 버튼 누름에 응답하려면 수신된 데이터가 button 속성이 있는 object인지 확인하도록 readLoop를 업데이트하세요. 그런 다음 buttonPushed를 호출하여 버튼 푸시를 처리합니다.
script.js - readLoop()
const { value, done } = await reader.read();
if (value && value.button) {
buttonPushed(value);
} else {
log.textContent += value + '\n';
}
micro:bit 버튼을 누르면 LED 매트릭스의 디스플레이가 변경되어야 합니다. 다음 코드를 사용하여 매트릭스를 설정합니다.
script.js - buttonPushed()
// CODELAB: micro:bit button press handler
if (butEvt.button === 'BTN1') {
divLeftBut.classList.toggle('pressed', butEvt.pressed);
if (butEvt.pressed) {
drawGrid(GRID_HAPPY);
sendGrid();
}
return;
}
if (butEvt.button === 'BTN2') {
divRightBut.classList.toggle('pressed', butEvt.pressed);
if (butEvt.pressed) {
drawGrid(GRID_SAD);
sendGrid();
}
}
직접 해 보기
이제 micro:bit 버튼 중 하나를 누르면 LED 매트릭스가 웃는 얼굴이나 슬픈 얼굴로 바뀝니다.
- 페이지를 새로고침합니다.
- 연결 버튼을 클릭합니다.
- 직렬 포트 선택기 대화상자에서 BBC micro:bit 기기를 선택하고 연결을 클릭합니다.
- 마이크로비트 LED 매트릭스에 웃는 얼굴이 표시됩니다.
- micro:bit의 버튼을 누르고 LED 매트릭스가 변경되는지 확인합니다.
8. 직렬 포트 닫기
마지막 단계는 사용자가 완료되면 포트를 닫도록 연결 해제 기능을 연결하는 것입니다.
사용자가 연결/연결 해제 버튼을 클릭하면 포트 닫기
사용자가 연결/연결 해제 버튼을 클릭하면 연결을 닫아야 합니다. 포트가 이미 열려 있는 경우 disconnect()를 호출하고 페이지가 더 이상 직렬 기기에 연결되어 있지 않음을 나타내도록 UI를 업데이트합니다.
script.js - clickConnect()
// CODELAB: Add disconnect code here.
if (port) {
await disconnect();
toggleUIConnected(false);
return;
}
스트림과 포트 닫기
disconnect 함수에서 입력 스트림을 닫고 출력 스트림을 닫고 포트를 닫아야 합니다. 입력 스트림을 닫으려면 reader.cancel()를 호출합니다. cancel 호출은 비동기식이므로 await를 사용하여 완료될 때까지 기다려야 합니다.
script.js - disconnect()
// CODELAB: Close the input stream (reader).
if (reader) {
await reader.cancel();
await inputDone.catch(() => {});
reader = null;
inputDone = null;
}
출력 스트림을 닫으려면 writer를 가져오고 close()를 호출하고 outputDone 객체가 닫힐 때까지 기다립니다.
script.js - disconnect()
// CODELAB: Close the output stream.
if (outputStream) {
await outputStream.getWriter().close();
await outputDone;
outputStream = null;
outputDone = null;
}
마지막으로 직렬 포트를 닫고 닫힐 때까지 기다립니다.
script.js - disconnect()
// CODELAB: Close the port.
await port.close();
port = null;
직접 해 보기
이제 원하는 대로 직렬 포트를 열고 닫을 수 있습니다.
- 페이지를 새로고침합니다.
- 연결 버튼을 클릭합니다.
- 직렬 포트 선택기 대화상자에서 BBC micro:bit 기기를 선택하고 연결을 클릭합니다.
- micro:bit LED 매트릭스에 웃는 얼굴이 표시됩니다.
- 연결 해제 버튼을 누르고 LED 매트릭스가 꺼지고 콘솔에 오류가 없는지 확인합니다.
9. 축하합니다
축하합니다. Web Serial API를 사용하는 첫 번째 웹 앱을 성공적으로 빌드했습니다.
https://goo.gle/fugu-api-tracker에서 Chrome팀이 작업 중인 Web Serial API 및 기타 흥미로운 새로운 웹 기능에 관한 최신 정보를 확인하세요.