PC 프로그램 신규 하드웨어 등록
이 문서는 2023년 3월 14일에 작성되었습니다.
여기서 말하는 PC 프로그램
이란? 하드웨어를 제어하기 위한 PC용 프로그램입니다. electron
기반으로 구현되었습니다.
신규 하드웨어 작성 및 배포 절차
신규 하드웨어를 추가하는 절차는 다음과 같습니다.
- PC 프로그램에서 하드웨어 제어 로직을 작성하고
- 블록 공작소에서 블록을 제작
- PC 프로그램과 블록 연동 테스트
- 코디니 사업팀에 승인 요청(단, 승인은 최초 한번만 필요합니다)
- 배포: 승인이 된 후에, 블록 공작소에서 배포하면 일반 사용자에게 즉시 노출됩니다.
- 수정 배포: 수정사항이 있으면, 수정후에 다시 배포하시면 됩니다. 승인과정은 필요없습니다.
신규 하드웨어 작성
- 모든 하드웨어에는
하드웨어 ID(hwId)
를 부여합니다. src/custom/hw
폴더에hwId
폴더를 만드세요.- hwId는 원하는 것으로 결정하세요. 중복되지만 않으면 문제되지 않습니다.
- 만약 hwId가
awesome
인 하드웨어를 추가한다면 src/custom/hw/awesome
폴더를 만들면 됩니다.
hwId
규칙
hwId
는 소문자로 시작해야 하고, 공백이 없습니다.- 알파벳만 가능합니다. 하이픈(
-
)이나 언더바(_
)는 사용할 수 없습니다. camel case
표기법을 권장합니다.- 예를 들면 아래와 같은 하드웨어 ID들이 있습니다.
- exMarsCube
- saeonAltinoLite
- wiseXboard
작성할 내용
-
hwId
가awesome
인 하드웨어를 예로 든다면, -
구현할 내용은
openDevice.ts
,IAwesomeControl.ts
,AwesomeControl.ts
,index.ts
파일입니다.openDevice.ts
파일은 시리얼 장치를 오픈하는 로직이 포함됩니다. 시리얼포트의baudRate
나COM1
같은path
를 설정합니다.IAwesomeControl.ts
은 하드웨어를 제어하는 함수들의 인터페이스이고AwesomeControl.ts
은 하드웨어 인터페이스의 구현체입니다. 실제로 하드웨어와 통신하는 코드가 포함됩니다.index.ts
는 기타 부가적인 정보와 함께 인터페이스와 구현체를export
합니다.
기본적인 샘플 소스코드
src/custom/hw/awesome/openDevice
.ts 파일
openDevice.ts
파일에는 2개의 함수를 구현해야 합니다.isPortMatch()
,openDevice()
함수입니다.isPortMatch()
는awesome
하드웨어가 주어진 시리얼 포트를 지원하는지 여부를 체크하는 함수입니다.- 지원한다면 true를 리턴합니다.
- 보통 시리얼 포트 드라이버의 제조사명으로 지원여부를 체크합니다.
- 아래의 예제는
CP210
드라이버 제조사인silicon labs
를 체크하고 있습니다. - 개발중에는 무조건 true를 리턴해도 괜찮지만, 릴리즈할 때는 적절하게 작성하는 것이 사용자 편의를 위해 좋습니다.
- 두번째 구현할
openDevice()
함수는awesome
하드웨어의 시리얼포트를 open하는 로직을 작성하는 함수입니다.baudRate
,parity
같은 시리얼 포트 옵션 정보들을 설정할 수 있습니다.- 아래의 예에서는
baudRate
를38400
으로 설정하고 있고,parity
는 생략했으므로, 기본값인none
이 사용됩니다.
import { DelimiterParser, SerialPort } from 'serialport'
import { ISerialDeviceOpenParams, ISerialPortInfo } from 'src/custom-types'
import { SerialDevice } from 'src/hw-server/serialport/SerialDevice'
// 디버깅용 로그 태그
const DEBUG_TAG = 'awesome'
/**
* 지원하는 시리얼포트 여부 체크
*
* @param portInfo 포트 정보
* @returns 지원하는 포트라면 true를 리턴
*/
export function isPortMatch(port: ISerialPortInfo): boolean {
// const { manufacturer, productId, vendorId } = port
const { manufacturer = '' } = port
// silicon labs(CP210)만 허용
const matched = manufacturer.toLowerCase().indexOf('silicon labs') >= 0
return matched
}
/**
* 시리얼 디바이스 오픈
* open 중인 상태의 SerialDevice를 리턴합니다.
*
* 연결이 되기를 기다리려면
* await device.waitUntilOpen()
*
* @param serialPortPath 시리얼포트 Path, ex) COM1, /dev/ttyUSB0
* @param uiLogger UI 콘솔 로거, 사용자 화면에 로깅 메시지를 표시합니다
* @returns SerialDevice
*/
export function openDevice(params: ISerialDeviceOpenParams): SerialDevice {
const { serialPortPath, uiLogger } = params
console.log(DEBUG_TAG, 'openDevice()', serialPortPath)
// 시리얼 디바이스 생성, 시리얼포트를 감싸는 객체입니다.
// 실제 serial port의 상태를 관리하고, UI에 로그를 전송합니다.
const device = new SerialDevice(DEBUG_TAG, uiLogger)
// 시리얼 포트 생성
const port = new SerialPort({
path: serialPortPath,
baudRate: 38400,
autoOpen: false, // autoOpen은 반드시 false
})
// 시리얼 디바이스 열기
device.open(port)
// open 중인 상태의 SerialDevice를 리턴합니다.
return device
}
- 위의 코드에서
openDevice()
함수에uiLogger
변수가 있습니다.uiLogger
는하드웨어 제어 콘솔
에 로그를 남기는 용도입니다. - 아래 그림에서
하드웨어 제어 콘솔
예시를 보여주고 있습니다.
src/custom/hw/awesome/IAwesomeControl
.ts 파일
IAwesomeControl.ts
파일은 하드웨어를 제어하는 함수들의 인터페이스입니다.- 함수의 이름과 인자는 목적에 맞게 자유롭게 지정할 수 있습니다.
예를 들어, 아래의
digitalRead(pin)
는 인자로 주어진 핀번호에서 디지털 값을 읽는 기능을 합니다. - 블록코딩에서 호출할 함수이므로, 모두
Promise
를 리턴해야 합니다. - 주의할 점: 블록 코딩의 블록에서 인터페이스의 함수를 호출하는 방식으로 동작하는데, 블록 코딩의 한 블록이, 2개의 함수를 호출할 수 없습니다. 한 블록 당 1개의 함수를 호출한다는 점을 유의해서, 함수를 설계해주세요. 즉, 블록과 함수는 일대일 매핑입니다.
// file: src/custom/hw/awesome/IAwesomeControl.ts
/**
* 컨트롤 인터페이스
*/
export interface IAwesomeControl {
digitalRead(pin: number): Promise<number>
digitalWrite(pin: number, value: number): Promise<void>
}
src/custom/hw/awesome/AwesomeControl
.ts 파일
- src/custom/hw/awesome/
IAwesomeControl
인터페이스의 구현 부분입니다. IAwesomeControl
인터페이스의 함수 2개를 구현해야 합니다.
// file: src/custom/hw/awesome/AwesomeControl.ts
import { AbstractHwConrtol } from '../AbstractHwControl'
import { IAwesomeControl } from './IAwesomeControl'
const chr = (ch: string): number => ch.charCodeAt(0)
/**
* 하드웨어 서비스
*/
export class AwesomeControl extends AbstractHwConrtol implements IAwesomeControl {
/**
* IAwesomeControl 인터페이스 구현
*/
digitalRead = async (ctx: any, pin: number): Promise<number> => {
const values = await this.readNext_(ctx)
// [pin1 ~ pin5]
const v = values[pin - 1]
return v > 100 ? 1 : 0
}
/**
* IAwesomeControl 인터페이스 구현
*/
digitalWrite = async (ctx: any, pin: number, value: number): Promise<void> => {
value = value > 0 ? 1 : 0
const pkt = [chr('X'), chr('R'), 2, 0, 0, 0, 0, 0, chr('S')]
pkt[2 + pin] = value
await this.write_(ctx, pkt)
}
}
- 위의 코드에서 주목할 부분은
this.readNext_()
와this.write_()
입니다. 이 함수는 부모 클래스인AbstractHwConrtol
에 구현되어 있습니다. readNext_()
함수는 시리얼포트에서 다음 데이터를 읽는 함수입니다.write_()
함수는 시리얼포트에 데이터를 쓰는 함수입니다.Buffer
또는number[]
를 쓸 수 있습니다.
src/custom/hw/awesome/index
.ts 파일
- 이제, 지금까지 작성한 정보를
index.ts
파일에 정리할 단계입니다. - 하드웨어 ID(
hwId
)와 하드웨어 이름(hwName
)을 설정해주세요. - 하드웨어 종류(
hwKind
)는serial
고정값입니다. - 필요한 USB 드라이버가 다르다면,
public/drivders
폴더에 설치파일을 복사한 후에 적어주세요.
// file: src/custom/hw/awesome/index.ts
import { HwKindKey, IHwDescriptor, IHwInfo } from 'src/custom-types/basic-types'
import { isPortMatch, openDevice } from './openDevice'
import { AwesomeControl } from './AwesomeControl'
const hwId = 'awesome'
const hwKind: HwKindKey = 'serial'
const info: IHwInfo = {
hwId,
hwKind,
hwName: '어썸보드',
category: 'module',
supportPlatforms: ['win32'],
pcDrivers: [
{
name: 'USB 드라이버',
'win32-ia32': 'CP210x_Universal_Windows_Driver/CP210xVCPInstaller_x86.exe',
'win32-x64': 'CP210x_Universal_Windows_Driver/CP210xVCPInstaller_x64.exe',
},
],
}
export const awesome: IHwDescriptor = {
hwId, // 하드웨어 ID
hwKind, // 하드웨어 종류
info, // 하드웨어 정보
hw: {
hwId,
hwKind,
isPortMatch, // 시리얼포트 지원 함수
createControl: () => new AwesomeControl(),
openDevice,
},
}
하드웨어 익스포트
- 여기까지 커스텀 하드웨어 추가하는 과정을 살펴보았습니다. 하지만 아직 PC 프로그램의 하드웨어 목록에서는 조회되지 않습니다.
- 추가한
awesome
하드웨어를 하드웨어 목록에 등록해야 합니다. - 아래와 같이
src/custom/hw/index.ts
파일에 등록하면 됩니다.
// file: src/custom/hw/index.ts
import { wiseXboard } from './hw/wiseXboard'
import { wiseXboardPremium } from './hw/wiseXboardPremium'
import { exMarsCube } from './hw/exMarsCube'
import { saeonAltinoLite } from './hw/saeonAltinoLite'
import { awesome } from './hw/awesome'
import { IHwDescriptor } from 'src/custom-types'
export const HardwareDescriptors: Record<string, IHwDescriptor> = {
[wiseXboardPremium.hwId]: wiseXboardPremium,
[wiseXboard.hwId]: wiseXboard,
[exMarsCube.hwId]: exMarsCube,
[saeonAltinoLite.hwId]: saeonAltinoLite,
// 추가한 awesome 하드웨어
[awesome.hwId]: awesome,
}
하드웨어의 조회 및 이미지
- 이제 PC 프로그램을 실행해서 하드웨어가 조회되는지 확인해보세요.
- 만약 하드웨어의 이미지가 표시되지 않는다면, 이미지를 등록해주세요.
- 하드웨어 ID가
awesome
이라면public/static/images/devices/awesome.png
경로에 하드웨어의 이미지를 저장하시면 됩니다. 하드웨어의 ID와 이름이 같은 png 파일이어야 합니다.
다음 단계
- 간단하게 하드웨어를 추가하는 방법을 살펴보았습니다.
- 하지만 아직 실제로 적용하기에는 부족합니다. 가령 시리얼포트로부터 수신된 데이터를 파싱해야 할 수도 있습니다.
- 하드웨어에 따라 수신하는 방식이 다르지만, 크게 두 가지로 분류할 수 있습니다.
- 블록코딩에서 명령을 수신했을때만 시리얼포트로부터 읽기
- 명령의 수신 여부와 상관없이 계속해서 수신하기
- 다음
하드웨어 데이터 송신
문서를 읽어주세요.