엔트리 하드웨어 개발 튜토리얼

개요

이 페이지는 엔트리 하드웨어 개발을 위해 Docs 를 보시는 분들이 많다는 것을 감안해 최상단 페이지에 만들어진 튜토리얼입니다.

엔트리 하드웨어 개발은 아래와 같은 순서로 진행됩니다.

  1. Entry Js를 이용하여 블록을 작성
  2. Entry Hardware를 이용하여 하드웨어 조작 모듈 작성
  3. 정상적으로 구성된 코드를 Git을 통해 반영 요청

엔트리 코드를 작성하는 과정에서 Git과 JavaScript를 사용하기 때문에 이를 먼저 익히는 것을 권장합니다. (Git 사용 방법)

하드웨어 등록 신청하기

연관 페이지: 하드웨어 등록 서비스 신청하기

엔트리에 하드웨어를 등록하려면 하드웨어 등록 서비스를 신청하고 하드웨어 아이디를 발급받아야 합니다.
그러나 개발 단계에서는 하드웨어 아이디가 필요하지 않으므로 이 페이지에서는 더미값을 사용합니다.

개발 환경 설정

Git 설치 및 GitHub 회원 가입

Git 설치

연관 페이지: Git 사용 방법

엔트리의 오픈소스 프로젝트는 Git을 사용하여 관리합니다. Git을 사용하여 코드를 기록하고 오픈소스에 반영하므로 반드시 Git을 설치해야 합니다.


GitHub 회원 가입

GitHub은 Git을 사용해 관리할 수 있는 원격 저장소입니다.
엔트리 프로젝트 또한 GitHub에 저장소가 있습니다.
다음과 같은 정보를 입력하여 쉽게 GitHub에 회원 가입할 수 있습니다.

tutorial01

개발 환경 설정


Node.js 설치

Node.js를 설치하려면 Node.js에서 설치 파일을 다운로드하여 실행합니다.

버전은 개발 시 크게 상관 없으며, LTS 버전을 설치하시면 됩니다.

Node.js를 설치하면 npm이 함께 설치됩니다.
npm을 이용하여 라이브러리를 설치하고 관리할 수 있습니다.

설치가 완료되면 명령 프롬프트 창에서 다음 명령어를 입력해 정상적으로 설치되었는지 확인합니다.

npm --version
node --version

정상적으로 설치되었다면 다음과 같은 결과가 출력됩니다. 버전은 다를 수 있습니다.

tutorial03


Yarn 설치 (optional)

엔트리 팀은 패키지 매니저로 yarn 을 사용합니다. 그러므로 yarn 을 통해 의존성 관리를 하는 것을 추천드립니다.
필수사항은 아니므로 넘어가도 상관없습니다.

yarn 을 설치한 경우, npm installnpm run 명령어는 yarn 으로 대체됩니다.
(npm install -> yarn, npm run start -> yarn start)

npm install --global yarn

Node-gyp 설치

엔트리 하드웨어는 시리얼포트 통신을 위해 node-serialport 라이브러리를 사용합니다.
해당 라이브러리를 사용하기 위해서는 C++, python 빌드 환경과 node-gyp 라이브러리가 필요합니다.
빌드에 대한 자세한 사항은 node-gyp#installation 을 참고해 주세요.

윈도우의 경우는 아래의 명령을 통해 파이썬, 윈도우 C++ 관련 툴을 설치해주세요. (관리자 모드 프롬프트에서 입력하세요)

npm install --global --production windows-build-tools

그 다음 빌드 라이브러리인 node-gyp 을 설치해주세요.

npm install --global node-gyp

엔트리 프로젝트 설치


원격 저장소의 데이터를 자신의 원격 저장소에 복사

엔트리 하드웨어 개발에는 다음과 같은 엔트리 프로젝트가 필요합니다.

엔트리 하드웨어 개발은 두 개의 프로젝트를 자신의 원격 저장소에 그대로 복사(fork)하여 작업한 후 작업 내역을 합병 요청하는 방식으로 이루어집니다.

두 저장소에 접속한 후 오른쪽 위의 Fork를 클릭하여 자신의 원격 저장소로 복사합니다.

tutorial04


자신의 원격 저장소 데이터를 자신의 컴퓨터로 복사

다음 명령어를 실행하여 자신의 원격 저장소 데이터를 자신의 컴퓨터로 복사(clone)합니다. 이 명령은 명령 프롬프트 또는 Git Bash에서 실행할 수 있습니다.

git clone https://github.com/[사용자명]/entry-hw.git
git clone https://github.com/[사용자명]/entryjs.git

브랜치 변경

하드웨어 블록을 개발하는 경우 develop-hw라는 브랜치를 사용해야 합니다.
develop-hw 브랜치는 하드웨어 관련 PR 이 모여서, 배포전에 develop 에 merge 되는 용도로 사용 중입니다.
(develop 브랜치는 엔트리 개발팀에서 이슈 처리를 하는 브랜치 입니다.)

Git은 작업 내역을 분할/관리하기 위해 브랜치라는 개념을 사용합니다.
자세한 설명은 Git - 브랜치란 무엇인가?를 참고하세요.

각 프로젝트의 디렉토리에서 다음 명령어를 실행하여 develop-hw 브랜치를 사용하게 설정합니다.

git checkout develop-hw

브랜치를 변경하면 다음과 같은 결과가 출력됩니다.

tutorial15

결론적으로는,
entryjs, entry-hw 모두 develop-hw 브랜치에서 작업하시면 됩니다.


의존성 라이브러리 설치하기

엔트리 프로젝트를 실행하려면 추가 라이브러리가 필요합니다. npm을 이용하여 필요한 라이브러리를 설치할 수 있습니다.

entryjs 디렉터리에서 다음 명령어를 실행합니다.

npm install

entry-hw 디렉터리에서 다음 명령어를 실행합니다.

npm install
npm run setting

각 명령어를 실행하면 다음과 같은 화면이 출력됩니다.

tutorial16

실행 테스트

entryjs 디렉터리에서 다음 명령어를 실행합니다.

npm run serve

명령어를 실행하면 다음과 같은 화면이 출력됩니다.

tutorial06

npm run serve는 개발 테스트 시에 사용하는 명령어로, 실시간으로 코드가 반영되는 환경에서 테스트할 수 있습니다.

entry-hw 디렉터리에서 다음 명령어를 실행합니다.

npm run start

명령어를 실행하면 하드웨어가 정상적으로 연결된 경우 다음과 같은 화면이 출력됩니다.

tutorial07

하드웨어가 정상적으로 연결되지 않은 경우에는 하드웨어 연결하기를 클릭합니다.

블록 개발

연관 페이지: 블록 명세 작성, 하드웨어 블록 번역작업

이 튜토리얼에서는 ‘Testino’라는 하드웨어의 특정 디지털 포트의 값을 on/off 설정하는 ‘testino_on_digital_value’라는 블록을 개발합니다.

편리한 개발을 위해 Visual Studio Code 편집기 사용을 추천합니다.

블록 작성하기

연관 페이지: 하드웨어 블록 만들기 - 값 읽고 쓰기

Entry.hw.sendQueue: 하드웨어에 보낼 값
Entry.hw.portData: 하드웨어에서 보낸 값
Entry.hw.update(): 하드웨어로 데이터를 바로 보냄

하드웨어를 사용하는 블록은 [entryjs 경로]/src/playground/blocks/hardware/ 디렉터리에 block_하드웨어명.js 파일로 작성해야 합니다. 여기에서는 block_testino.js 파일에 작성합니다.


기본 명세

블록 모듈파일은 기본적인 프로퍼티와 메소드명을 준수하여 개발하셔야 합니다.
엔트리는 정해진 프로퍼티를 읽어와 로직을 수행합니다.
기본적인 프로퍼티는 아래와 같습니다.
id 프로퍼티의 경우, 엔트리에서 하드웨어 고유번호를 발급받아야 하지만 여기서는 테스트용으로 더미 ID 를 사용하였습니다.

blockMenuBlocks 프로퍼티에 대한 자세한 설명은 블록 사용 등록 을 참고해주세요.

class Testino {
constructor() {
this.id = 'FF.FF';
this.url = 'http://www.my-company.org/';
this.name = 'Testino';
this.imageName = 'testino.png'; //thumbnail
this.title = {
ko: '테스트이노',
en: 'testino',
};

// 블록메뉴에 표기될 블록명을 추가합니다. 이곳에 추가하지 않으면 블록이 표기되지 않습니다.
this.blockMenuBlocks = ['testino_on_digital_value', 'testino_off_digital_value'];
}
}

엔트리 정지시 데이터 정리 로직 추가

엔트리 프로젝트가 정지될 때, 엔트리는 하드웨어 블록모듈의 setZero 라는 이름의 함수를 호출합니다.
이곳에서 포트 상태를 초기화 하거나, 특정 패킷을 보내 디바이스를 리셋하는데 사용할 수 있습니다.

class Testino {
constructor() { /* ... */}
setZero() {
// 엔트리 실행이 정지되었을 때 보낼 신호(reset 명령을 보냄)
// 2번부터 13번 포트를 0으로 초기화
for(let i = 2 ; i <= 13 ; i++) {
Entry.hw.sendQueue.PORT[i] = 0;
}

Entry.hw.update(); // 하드웨어에 명시적으로 정보를 보냄.
}
}

다국어 명세

연관 페이지: 하드웨어 블록 번역작업

블록이 다국어를 지원하도록 setLanguage 함수를 작성해야 합니다. 한국어(ko)와 영어(en)는 반드시 작성해야 합니다. 블록에 표시될 문구를 언어별로 작성합니다.

파라미터값을 문구에 넣으려면 %1, %2와 같은 값을 사용합니다. 파라미터값이 다국어를 지원하게 하려면 template:{}과 동일한 수준의 Blocks:{} 안에 파라미터값과 해당하는 문구를 작성합니다. 여기에서는 파라미터값으로 숫자만 사용하기 때문에 Blocks:{}는 작성하지 않았습니다.

class Testino {
constructor() { /* ... */}
setZero() { /* ... */ }
setLanguage() {
return {
ko: {
template: {
testino_on_digital_value: '디지털 핀 %1 번을 켜기 %2',
testino_off_digital_value: '디지털 핀 %1 번을 끄기 %2',
},
},
en: {
template: {
testino_on_digital_value: 'turn on digital pin %1 %2',
testino_off_digital_value: 'turn off digital pin %1 %2',
},
},
};
}
}

블록 명세

연관 페이지: 블록 명세 작성, 블록 모양별 개발 방법

실제 블록을 작성하는 부분입니다. getBlocks 함수를 템플릿에 맞춰 작성합니다.

여기에서는 가장 기초적인 부분만을 설명하므로 실제 개발 시에는 기존에 개발된 블록 코드를 참고하시는 것이 좋습니다. arduino_ext, EV3 등 블록 관련 기존 코드는 [entryjs 경로]/src/playground/blocks 디렉터리에 있습니다.

class Testino {
constructor() { /* ... */}
setZero() { /* ... */ }
setLanguage() { /* ... */ }
getBlocks() {
return {
testino_on_digital_value: {
color: EntryStatic.colorSet.block.default.HARDWARE,
outerLine: EntryStatic.colorSet.block.darken.HARDWARE,
fontColor: '#ffffff',
skeleton: 'basic', // 블록 모양 템플릿. 자세한 목록은 docs 를 참고해주세요
statements: [],
params: [
//입력될 파라미터들의 속성을 정의
{
type: 'Block',
accept: 'string', //숫자만 들어가도 string 입니다. 엔트리엔 이를 구분하지 않습니다.
},
// basic skeleton 의 마지막엔 인디케이터를 추가해주셔야 합니다.
{ type: 'Indicator', img: 'block_icon/hardware_icon.svg', size: 12 },
],
def: {
params: [
//파라미터에 들어갈 기본 값.
{
type: 'number',
params: [2],
},
],
type: 'testino_on_digital_value', // 블록 상속과 관련된 값입니다. 블록명과 동일하게 해주면 됩니다.
},
paramsKeyMap: {
// 실제 블록의 로직인 func 에서 해당 인덱스의 파라미터를 가져올때 쓸 key 값
PORT: 0,
},
events: {},
class: 'TestinoBlock', // 블록을 묶어서 보여줄 단위값. 이 값이 바뀌면 사이에 가로줄이 생깁니다.
isNotFor: ['Testino'], // 하드웨어가 연결되었을 경우만 블록을 보여주겠다는 판단값입니다. name 과 동일해야 합니다.
func: (sprite, script) => {
// paramsKeyMap 에서 PORT 는 파라미터의 0번 인덱스 값이었습니다.
const portNumber = script.getNumberValue('PORT');
Entry.hw.sendQueue[portNumber] = 1;
// 값을 반환해야하는 경우는 return 할 수 있습니다.
},
syntax: {
// 파이썬 문법 변환에 사용되고 있습니다.
js: [],
py: [
{
syntax: 'Testino.turnOnDigitalPort(%1)',
blockType: 'param',
textParams: [
{
type: 'Block',
accept: 'string',
},
],
},
],
},
},
testino_off_digital_value: {
color: EntryStatic.colorSet.block.default.HARDWARE,
outerLine: EntryStatic.colorSet.block.darken.HARDWARE,
fontColor: '#ffffff',
skeleton: 'basic',
statements: [],
params: [
{
type: 'Block',
accept: 'string',
},
{ type: 'Indicator', img: 'block_icon/hardware_icon.svg', size: 12 },
],
def: {
params: [
{
type: 'number',
params: [2],
},
],
type: 'testino_off_digital_value',
},
paramsKeyMap: {
PORT: 0,
},
events: {},
class: 'TestinoBlock',
isNotFor: ['Testino'],
func: (sprite, script) => {
const portNumber = script.getNumberValue('PORT');
Entry.hw.sendQueue[portNumber] = 0;
},
syntax: {
js: [],
py: [
{
syntax: 'Testino.turnOffDigitalPort(%1)',
blockType: 'param',
textParams: [
{
type: 'Block',
accept: 'string',
},
],
},
],
},
},
};
}
}

하드웨어 블록 모듈 등록

entryjs 에서 작성한 블록 모듈을 코드에서 export 해주면 entryjs 에서 자동으로 모듈을 인식하여 블록을 등록하게 됩니다.

파일 맨 아래에 아래와 같이 입력합니다.

Entry.Testino = new Testino();
module.exports = Entry.Testino;

이 코드를 참고하여 testino_off_digital_value 블록을 작성해 보세요(결과 파일 다운로드).

임시 블록 테스트

하드웨어를 연결하기 전, 블록이 정상적으로 만들어졌는지 테스트합니다.

isNotFor 속성값에 따라 해당 하드웨어가 연결된 경우에만 블록을 보여주므로, 하드웨어가 연결되지 않은 상태에서도 블록을 확인할 수 있도록 다음과 같이 주석 처리하겠습니다.

// isNotFor: ['Testino'], // 하드웨어가 연결되었을 경우만 블록을 보여주겠다는 판단값입니다. 기본 명세의 name값과 동일해야합니다.

entryjs 디렉터리에서 npm run serve를 실행하면 다음과 같이 블록이 표시되는 것을 확인할 수 있습니다.

tutorial08

확인 후에는 isNotFor 속성을 다시 주석 처리 해제합니다.

하드웨어 모듈 개발

지금까지 Entry Js를 이용하여 블록이 하드웨어 연결 프로그램과 통신하는 부분을 작성했습니다. 이제 Entry Hardware를 이용하여, 블록이 보낸 정보를 수신하고 가공하여 아두이노를 조작하는 방법을 알아보겠습니다.

하드웨어 모듈 구성

연관 페이지: 하드웨어 모듈 추가하기

하드웨어 모듈에는 파일이 세 개 필요합니다. 세 파일은 모두 이름이 같아야 합니다. 여기에서는 testino라고 하겠습니다. 다음과 같은 세 파일을 [entry-hw 경로]/app/modules 디렉터리에 작성합니다.

이 밖에 드라이버나 펌웨어가 필요할 수 있습니다.


이미지 파일 추가

다음과 같은 testino.png 파일을 추가합니다.

tutorial09

파일 크기가 너무 크거나 직사각형이면 문제가 발생할 수 있습니다. 이미지 파일에 대한 자세한 내용은 이미지 삽입을 참고하세요.


하드웨어 명세 추가

연관 페이지: .json 파일 생성

하드웨어의 드라이버나 펌웨어, 통신 방식 등 다양한 하드웨어 관련 명세를 작성하는 파일입니다. 속성에 대한 자세한 설명은 연관 페이지를 참고하세요.

{
"id": "FFFF01",
"name": {
"en": "Testino",
"ko": "테스트이노"
},
"category": "board", //board, robot, module 중 하나
"platform": ["win32", "darwin"],
"icon": "testino.png",
"module": "testino.js",
"url": "http://playentry.org",
"driver": { // 기존 아두이노 데이터를 활용
"win32-ia32": "arduino/dpinst-x86.exe",
"win32-x64": "arduino/dpinst-amd64.exe"
},
"reconnect": true,
"firmware": "board2", // 기존 아두이노 데이터를 활용
"hardware": { // 기존 아두이노 데이터를 활용
"type": "serial", // serial or bluetooth
"control": "slave", // master or slave. 일반적으로는 slave 입니다.
"duration": 32, // slave 모드일때 기기에 데이터를 요청하는 ms 간격
"vendor": "Arduino", // usb vendor 가 동일한 경우 바로 연결합니다.
"baudRate": 9600,
"firmwarecheck": true // 펌웨어가 없어서 연결이 안되는 경우 자동으로 펌웨어 업데이트 로직으로 이동할지 여부
}
}

하드웨어 로직 추가

연관 페이지: .js 파일 생성

엔트리 블록 뭉치 실행이 전부 끝나면 엔트리에서 설정된 데이터 요청 map이 하드웨어에 전송됩니다. 각 블록이 실행될 때마다 엔트리와 하드웨어 간 데이터가 송수신되는 방식이 아니므로 주의하시기 바랍니다.

바이트 버퍼의 내용은 하드웨어 구현 방식에 종속되어 벤더별로 다릅니다. 이 정보는 엔트리에서 제공하지 않습니다. 이 코드는 단순히 디지털 신호를 아두이노에 보내는 역할만을 합니다.

모듈 내부의 코드는 전적으로 하드웨어 개발자에 의해 결정됩니다. 아래의 코드는 참고만 하시기 바랍니다. 하드웨어와 정상적인 통신이 가능하며 엔트리에 지장을 주지 않는 경우에 한해 자유롭게 작성하시면 됩니다.

const BaseModule = require('./baseModule');

class Module extends BaseModule {
constructor() {
super();
this.sp = null;
this.digitalPin = [];
this.sendBuffers = [];
}

init(handler, config) {
// 엔트리 브라우저와 연결되었을때 호출됨
}

setSerialPort(sp) {
// 최초 연결시도(handshake) 성공 후에 호출됨
this.sp = sp;
}

requestInitialData() {
// 최초 연결시도시 디바이스에 보낼 데이터. checkInitialData 가 선언되어있다면 필수
return null;
}

checkInitialData(data, config) {
// 최초 연결시도에서 디바이스의 데이터를 받아, 원하는 데이터가 맞는지 판단하는 로직
// requestInitialData 가 선언되어있다면 필수
return true;
}

// deprecated
afterConnect(connector, cb) {
// handshake 종료 후 정상 연결상태로 진입전에 호출됨. connector 와 UI state 를 강제변경할 수 있으나 비추천
connector.connected = true;
if (cb) {
cb('connected'); // 해당 string state 로 UI state 를 강제변경하나 문제를 일으킬 수 있습니다.
}
}

validateLocalData(data) {
// 해당 함수가 존재하면, 디바이스에서 데이터를 받아온 후 validate 를 거친다. 없으면 그대로 처리로직으로 진행한다.
return true;
}

requestRemoteData(handler) {
// 디바이스에서 데이터를 받아온 후, 브라우저로 데이터를 보내기 위해 호출되는 로직. handler 를 세팅하는 것으로 값을 보낼 수 있다.
// handler.write(key, value) 로 세팅한 값은 Entry.hw.portData 에서 받아볼 수 있다.
};

handleRemoteData(handler) {
// 엔트리 브라우저에서 온 데이터를 처리한다. handler.read 로 브라우저의 데이터를 읽어올 수 있다.
// handler 의 값은 Entry.hw.sendQueue 에 세팅한 값과 같다.
let buffer = new Buffer([]);
const digitalPin = this.digitalPin;

for (let i = 0 ; i < 14 ; i++) {
digitalPin[i] = handler.read(i);

buffer = Buffer.concat([
buffer,
this.makeOutputBuffer(1, i, digitalPin[i] === 1 ? 255 : 0),
]);
}

if (buffer.length) {
this.sendBuffers.push(buffer);
}
}

requestLocalData() {
// 디바이스로 데이터를 보내는 로직. control: slave 인 경우 duration 주기에 맞춰 디바이스에 데이터를 보낸다.
// return 값으로 버퍼를 반환하면 디바이스로 데이터를 보내나, 아두이노의 경우 레거시 코드를 따르고 있다.

if (this.sendBuffers.length > 0) {
this.sp.write(this.sendBuffers.shift(), () => {
if (this.sp) {
this.sp.drain(() => {
this.isDraing = false;
});
}
});
}

return null;
}

handleLocalData(data) {
// 디바이스에서 온 데이터를 처리하는 로직. 여기서는 처리할 데이터가 없어 스킵하였음.
}

disconnect(connector) {
// 커넥터가 연결해제될 때 호출되는 로직, 스캔 정지 혹은 디바이스 연결 해제시 호출된다.
connector.close();
if (this.sp) {
delete this.sp;
}
};

reset() {
// 엔트리 브라우저와의 소켓 연결이 끊어졌을 때 발생하는 로직.
}

// 이 아래로는 자유롭게 선언하여 사용한 함수입니다.
makeOutputBuffer(device, port, data) {
let buffer;
const value = new Buffer(2);
const dummy = new Buffer([10]);

value.writeInt16LE(data);
buffer = new Buffer([
255,
85,
6,
0, // sensorIdx
2,
device,
port,
]);
buffer = Buffer.concat([buffer, value, dummy]);

return buffer;
};

getDataByBuffer(buffer) {
const datas = [];
let lastIndex = 0;
buffer.forEach((value, idx) => {
if (value == 13 && buffer[idx + 1] == 10) {
datas.push(buffer.subarray(lastIndex, idx));
lastIndex = idx + 2;
}
});

return datas;
};
}

module.exports = new Module();

최종 확인

entryjs 디렉터리에서 npm run serve 명령을 실행합니다.

tutorial17

하드웨어 선택 창에 다음과 같이 테스트이노가 표시됩니다.

tutorial10

테스트이노를 클릭하면 하드웨어가 연결됩니다. 연결되지 않는 경우 드라이버를 설치합니다.

tutorial11

하드웨어가 연결되면 작성한 블록이 표시됩니다.

tutorial12

디지털 핀 4 번을 켜기 블록을 추가합니다.

tutorial18

블록을 추가하고 엔트리를 실행하면 디지털 핀 번호에 해당하는 LED 램프가 켜지는 것을 확인할 수 있습니다.

tutorial12

최종 산출물

최종 산출물은 다음과 같습니다.

코드 기록하기

작업이 완료되면 해당 작업을 Git에 기록(commit)하고, 기록한 작업을 원격 저장소에 업로드(push)합니다.
Git 사용에 익숙한 사용자는 Sourcetree나 IDE의 버전 관리 기능을 활용할 수 있습니다.
익숙하지 않은 경우 다음 명령어를 실행하면 코드를 자신의 컴퓨터에 기록하고 자신의 원격 저장소에 업로드할 수 있습니다.

반영하기

이 튜토리얼에서 작성한 코드는 실제로 반영하지 않으므로 이 페이지에서는 반영 방법을 설명하지 않습니다.
반영 방법은 반영하기를 참고하세요.