2016년 7월 28일 목요일

크롬 앱으로 멀티플랫폼에서 시리얼 통신하기 (Serial communication through Chrome App)

전자회로 개발 프로젝트를 진행하는 동안 때로 내가 만드는 디바이스와 시리얼로 통신을 하는 GUI를 개발할 필요가 있다.

과거에는 .Net Framework과 C#을 사용해 인터페이스를 만들었었다. Rapid protyping을 가능하게 하는 프레임웍을 사용하면 인터페이스를 커스텀화 하고 OS의 다양한 그래픽 요소를 쉽게 접근할 수 있게 해 준다.



하지만 문제는 그렇게 개발한 앱은 특정 환경에서만 사용할 수 있다는 것이다. 어떻게 하면 진정한 크로스 플랫폼, 즉 맥이나 리눅스에서도 실행될 수 있는가를 찾아 보았다. 거의 모든 운영체제에서 사용할 수 있는 Java나 Python 같은 언어를 사용할 수도 있다. 하지만 이 언어들로 GUI를 개발하는 것은 전용 프레임웍을 사용해야 한다. 추가적으로 SunOracle은 Java의 시리얼 통신 API(javax.comm)의 스펙만을 정의해 놓고 구현을 하지 않았다. 그러므로 이 API를 구현해 놓은 외부 라이브러리(가장 유명한 것이 RxTx이다)를 사용해야만 한다.

여기서 소개하는 방법은 외부 라이브러리를 사용하지 않고 웹 사이트를 개발할 때 사용하는 것과 동일한 기술로 크로스 플랫폼 앱을 개발하는 방법이다.

Chrome App


Chrome App은 기본적으로 어플리케이션이다. 하지만 공통적인 웹 기술(HTML, CSS, javascript등)을 사용해 개발되고 Chrome 브라우져 엔진에 의해 실행된다.
 
표준 웹 사이트와 비교했을 때 가장 큰 장점은 다음과 같다.

* Seamless desktop integration (no address bar...)
* Grouped in a dedicated launcher
* Ability to interface with hardware devices


Anatomy of a Chrome App


Chrome App은 크게 3가지 요소로 구성된다.

* manifest file: 앱의 메타 데이터 (이름, 버젼, 설명 등)를 가지고 있다.
* background script: 앱의 life cycle을 관리하기 위한 이벤트 페이지를 만든다.
* main interface: HTML 페이지, javascript 스크립트, CSS(Cascade Style Sheet) 등으로 만들어진다.

아두이노 IDE에 포함되어 있는 시리얼 모니터를 에뮬레이트하는 데모 앱을 준비해 보았다. 이 앱은 chrome web store에서 구할 수 있고, 소스코드는 github repository에서 다운받을 수 있다.

코드를 좀 상세히 보도록 하자.

manifest.json 파일은 메타 데이터를 포함하고 있다. 가장 중요한 것은 시리얼 포트를 억세스하기를 원한다는 것을 선언하는 것과 백그라운드 스크립트의 이름을 지정하는 것이다.



백그라운드 스크립트는 window.html 파일을 디스플레이 하기 위한 사이즈를 조정할 수 없는 윈도우 (600x500 픽셀)를 만든다.



메인 어플리케이션은 3개의 파일로 만들어 진다.

* window.html
* main.js
* main.css

또한 다양한 메시지 다이얼로그를 디스플레이 하기 위해 jQuery와, 플러그인인 jQuery simplemodal을 사용했다.

Serial port access


Javascript를 사용해 PC의 시리얼포트를 제어하기 위해 Chrome은 chrome.serial API를 제공한다.

먼저 사용 가능한 포트 목록을 찾아 콤보박스에 추가해서, 사용자가 원하는 포트를 선택할 수 있게 해 준다.

chrome.serial.getDevices(function(ports) {
  for (var i = 0; i < ports.length; i++) {
    var portName = ports[i].path;
    var newOption = '' + portName + '';
    var newOption = ' + portName + '">' + portName + '';
    $("#serial_ports_combobox").append(newOption);
  }
});




선택한 시리얼 포트에 연결하기 위해 connect 메소드를 사용한다.

chrome.serial.connect(selectedPort, connectionOptions, onConnect);

이 때 시리얼 포트 설정을 가지고 있는 배열(connectionOptions)을 넘겨줘야 한다.

var connectionOptions = {
  "bitrate": 9600,
  "dataBits": "eight",
  "parityBit": "no",
  "stopBits": "one",
  "receiveTimeout": 500,
  "sendTimeout": 500
};


연결되고 나면 send 메소드로 데이터를 보낼 수 있다.

chrome.serial.send(connectionId, convertStringToArrayBuffer(textToSend), function(sendInfo) {
  if(sendInfo.error) $.modal('Unable to send data: ' + sendInfo.error + '
')
});


에러가 발생하면 sendInfo.error가 null이 아닌 값을 가지게 된다. 위의 예제에서는 에러 메지지가 표시된다.

Javascript 언어의 주요 특징이 비동기성인데 이것이 바로 시리얼 포트로부터 데이터를 수신하는 패러다임이다. 먼저 onReceive 이벤트에 대한 listener(커스텀 함수)를 추가해 줘야만 한다.

chrome.serial.onReceive.addListener(onReceive);

시리얼 포트에 새 데이터가 수신될 때 마다 onReceive() 함수가 호출된다.

function onReceive(info) {
  if (info.connectionId == connectionId &amp;&amp; info.data) {
    var str = convertArrayBufferToString(info.data);
    $("#receive_textarea").append(str);
    $("#receive_textarea").scrollTop($("#receive_textarea")[0].scrollHeight);
  }
}


함수는 먼저 원하는 시리얼 포트에 유효한 데이터가 수신되었는지 확인한다. 유효한 데이터가 수신되면 ArrayBuffer 오브젝트를 string 으로 변환해 textArea에 추가한다.

Test


Chrome 브라우져 설정에서 'Developer mode'를 활성화 한 다음 'Load unpacked extension...'을 클릭하면 Chrome app을 테스트 할 수 있다.


앱을 포함하고 있는 폴더를 선택하면 거기에 있는 앱이 Chrome에 로드되어 실행할 수 있게 된다.

어플리케이션을 배포하고 싶으면 'Pack extension...' 버튼으로 installable package를 만들 수 있다. 또는 Chrome Developer program에 가입한 후 chrome web store에 업로드 할 수도 있다.




댓글 없음:

댓글 쓰기