오늘은 React(Vite)와 TypeScript를 활용하여 시리얼 통신을 구현하는 방법에 대해 알아보도록 하겠습니다. 최근 웹 개발에서 시리얼 포트를 다루는 경우가 많아지고 있는데요, Web Serial API를 통해 이를 손쉽게 처리할 수 있습니다. 그럼 시작해볼까요?😊
Serialport?
SerialPort는 시리얼 통신(Serial Communication)을 통해 데이터를 주고받기 위한 소프트웨어 인터페이스로, 주로 하드웨어 장치(센서, 마이크로컨트롤러, IoT 장치 등)와 컴퓨터 간의 데이터를 교환할 때 사용됩니다. 특히 임베디드 시스템, 하드웨어 제어 및 다양한 장치들과의 통신에 자주 활용됩니다. 이 과정에서 직렬 통신 포트(시리얼 포트)를 사용하여 바이트 단위의 데이터를 순차적으로 전송합니다.
시리얼 통신(Serial Communication)?
시리얼 통신(Serial Communication)이란 데이터를 한 번에 한 비트씩 순차적으로 전송하는 방식입니다. 이는 병렬 통신과 대비되며, 전송선이 적고 전송 거리가 길어도 안정적인 전송이 가능하다는 장점이 있습니다.
시리얼 통신은 RS-232, USB, SPI, I2C와 같은 여러 프로토콜을 통해 구현되며, 다양한 장치들과 통신할 수 있습니다.
Web Serial API
Web Serial API는 브라우저가 직렬 장치에 접근할 수 있도록 해주는 기능입니다. 이 API를 사용하면 웹 애플리케이션에서 직접 하드웨어 장치와 소통할 수 있게 됩니다.
(Web Serial API는 보안상의 이유로 HTTPS에서만 사용할 수 있습니다.)
환경 설정하기
개발관련 세팅이 되어있다는 가정하에 진행하도록 하겠습니다. (Node.js, npm, ...)
먼저, 프로젝트를 생성하고 필요한 패키지를 설치해야 합니다.
npm create vite@latest serial-communication --template react-ts
✔ Select a framework: › React
✔ Select a variant: › TypeScript
cd serial-communication
npm install
npm run dev
위와 같이 명령어를 작성하면 vite로 구성된 기본 화면이 출력됩니다. 이후 시리얼 통신을 위해서 최소한의 파일만 남겨두고 삭제하도록 하겠습니다.
UI 구성하기
시리얼 통신에는 포트 연결, 포트 열기, 포트에 데이터 쓰기, 포트에서 데이터 읽기 등과 같은 기능을 사용해야 합니다. 필요한 버튼 및 연결된 것을 알 수 있게 UI를 구성하겠습니다.
// App.tsx
import "./App.css";
function App() {
return (
<section>
<h1>serial-communication</h1>
<p>Selected Port: </p>
<div className="wrapper">
<button>Port select</button>
<button>Send Data</button>
</div>
</section>
);
}
export default App;
Serial Communication 구현하기
이제 본격적으로 시리얼 통신을 구현해보겠습니다! 아래 코드를 참고하세요:D
사용 가능한 포트 확인
시리얼 통신을 하기위해 사용 가능한 포트를 확인하여 연결하는 과정의 코드를 작성하겠습니다.
먼저 재사용성 있게 컴포넌트를 구성하기 위해 custom hook을 생성했습니다. 이때, Web Serial API를 사용하기 위해서는 기본적으로 비동기 통신이 되어야하는 것을 주의해서 작성해주시면 됩니다.
navigator.serial.requestPort(): 연결 가능한 포트 목록을 확인할 수 있도록 해주는 메서드입니다.
port.open({ option }): 직렬 포트에 맞는 option을 설정해서 시리얼 통신을 할 수 있도록 포트를 웹브라우저와 연결하는 메서드입니다.
// hooks/useSerialCommunication.tsx
import { useState } from "react";
function useSerialCommunication() {
const [port, setPort] = useState();
const initialize = async () => {
try {
// @ts-expect-error: navigator.serial is not yet supported in TypeScript
const port = await navigator.serial.requestPort();
setPort(port);
await port.open({ baudRate: 9600 });
} catch (err) {
console.error("포트 연결 실패:", err);
}
};
return { port, initialize };
}
export default useSerialCommunication;
// App.tsx
import "./App.css";
import useSerialCommunication from "./hooks/useSerialCommunication";
function App() {
const { port, initialize } = useSerialCommunication();
return (
<section>
<h1>serial-communication</h1>
<p>Selected Port: {port ? "connect" : ""}</p>
<div className="wrapper">
<button onClick={initialize}>Port select</button>
<button>Send Data</button>
</div>
</section>
);
}
export default App;
포트에 데이터 쓰기
sendData 함수는 React 애플리케이션에서 Web Serial API를 사용하여 시리얼 포트로 데이터를 전송하는 로직을 처리합니다. 이 함수는 사용자가 입력한 문자열 데이터를 시리얼 포트에 인코딩하여 전송하고, 오류 발생 시 적절히 로그를 출력합니다.
- 포트 선택 확인
port 객체가 없는 경우 에러를 던져 사용자가 시리얼 포트 연결을 먼저 설정하도록 요구합니다. - Writable 스트림 가져오기
port.writable.getWriter()를 호출해 데이터를 전송할 수 있는 Writable 스트림을 생성합니다. 스트림은 단일 작업을 수행하기 위해 잠금(lock) 상태가 되며, 작업이 완료된 후 releaseLock()으로 잠금을 해제해야 합니다. - 데이터 인코딩
사용자가 전송하고자 하는 데이터(messageToSend)는 문자열이므로, 시리얼 포트가 이해할 수 있는 바이너리 형식으로 변환해야 합니다. 이를 위해 Web API의 TextEncoder를 사용하여 문자열을 Uint8Array로 인코딩합니다. - 데이터 전송
writer.write(data)를 호출하여 인코딩된 데이터를 시리얼 포트로 전송합니다. - 스트림 해제
releaseLock()을 호출하여 스트림 잠금을 해제합니다. 이렇게 해야 다른 쓰기 작업이나 리소스 처리가 가능합니다. - 에러 처리
데이터 전송 중 문제가 발생하면 catch 블록에서 에러를 캡처하고, 오류 메시지를 출력합니다. - 디버깅 로그 출력
console.log를 사용해 전송된 데이터(messageToSend)와 변환된 바이너리 데이터(data)를 확인할 수 있도록 로그를 출력합니다.
const sendData = async (messageToSend: string) => {
if (!port) {
throw new Error("Port is not selected");
}
try {
// 시리얼 포트의 writable 스트림 가져오기
// @ts-expect-error: navigator.serial은 TypeScript에서 아직 지원되지 않음
const writer = port.writable.getWriter();
// TextEncoder를 사용해 문자열 데이터를 Uint8Array 형식으로 인코딩
const data = new TextEncoder().encode(messageToSend);
// 데이터를 시리얼 포트로 쓰기
await writer.write(data);
// writer 객체의 락 해제 (다른 쓰기 작업 가능하도록)
writer.releaseLock();
console.log("Data sent:", messageToSend, data);
} catch (error) {
// 데이터 쓰기 중 오류 발생 시 로그 출력
console.error("Error writing to serial port:", error);
}
};
포트에서 데이터 읽기
initialize 함수는 Web Serial API를 사용해 시리얼 포트를 초기화하고, 포트로부터 데이터를 읽는 로직을 구현합니다. 데이터는 TextDecoder를 사용해 문자열로 변환되며, React의 상태를 활용해 UI에서 확인할 수 있도록 처리됩니다.
- 포트 요청 및 초기화
- navigator.serial.requestPort()를 통해 사용자가 연결하려는 시리얼 포트를 선택합니다.
- 선택된 포트는 port.open({ baudRate: 9600 })를 호출해 특정 전송 속도로 초기화합니다.
- Readable 스트림 가져오기
- port.readable을 통해 포트의 Readable 스트림을 가져옵니다.
- getReader()를 호출해 데이터를 읽기 위한 Reader 객체를 생성합니다.
- 데이터 읽기
- reader.read()를 통해 데이터를 읽습니다.
- 반환값은 { value, done } 형식이며:
- value: Uint8Array 형식의 읽은 데이터
- done: 읽기가 종료되었는지 여부
- value가 존재하면 TextDecoder를 사용해 Uint8Array 데이터를 문자열로 변환하고, 이전 데이터에 추가하여 상태로 저장합니다.
- Reader 잠금 해제
- Reader는 단일 작업을 수행하기 위해 잠금(lock) 상태가 되므로, 작업이 끝나면 reader.releaseLock()을 호출해 잠금을 해제합니다.
- 에러 처리
- 포트 요청 실패, 포트 해제, 권한 부족 등의 에러를 try...catch 블록에서 처리하여 디버깅 로그를 출력합니다.
const initialize = async () => {
try {
// 시리얼 포트 요청 및 초기화
// @ts-expect-error: navigator.serial은 TypeScript에서 아직 지원되지 않음
const port = await navigator.serial.requestPort();
setPort(port); // 선택한 포트를 상태로 저장
await port.open({ baudRate: 9600 }); // 포트 열기
// 데이터 읽기 설정
const reader = port.readable?.getReader(); // 포트의 Readable 스트림 가져오기
if (reader) {
while (true) {
const { value, done } = await reader.read(); // 데이터 읽기
if (done) break; // 읽기가 종료되면 반복 종료
if (value) {
// Uint8Array 데이터를 문자열로 변환하고 상태 업데이트
setReceivedData((prev) => prev + new TextDecoder().decode(value));
}
}
reader.releaseLock(); // Reader 잠금 해제
}
} catch (err) {
console.error("포트 연결 실패:", err); // 에러 처리
}
};
Reference
- Web Serial API 공식문서
- https://stackoverflow.com/questions/68806607/using-web-serial-with-reactjs
'Coding > React & React-native' 카테고리의 다른 글
[React]React 19? (0) | 2024.02.29 |
---|---|
[RN]React Native CLI로 시작하기 - macOS(Android Studio) (0) | 2023.12.28 |
[RN] React Native란? (0) | 2023.12.22 |
[React] React Hook Form과 yup을 사용한 유효성 검사 (0) | 2022.11.21 |
[Redux] Redux-toolkit (0) | 2022.11.21 |