前回はRubyを使ってシリアルCOMポートからデータを受信してみましたが、他にも何か方法がないかを探してみたところ、ChromeAppsに chrome.serial API
がありました。
chrome.serial - Google Chrome
そこで、chrome.serial API を使って、シリアルCOMポートからデータを取ってみました。
なお、ChromeAppsの概要は以下のスライドが参考になりました。
Chrome Apps 概要
環境
- Windows7 x64
- Google Chrome バージョン 35.0.1916.114 m
用意するファイル
以下のBlogのファイルを参考に、いくつかファイルを用意しました。今回使用するファイルの名前もこのBlogに合わせてあります。
JavaScript + Chrome Packaged Apps API を使用したシリアル通信Webアプリ - Tech-Sketch
なお、png画像は以下のサイトで生成したものを使いました。ありがとうございます。
Placehold.jp|ダミー画像生成 モック用画像作成
manifest.json
Chrome拡張と同じく、manifest.jsonを用意します。
個人的に書き方で悩んだところとしては、
- manifest_versionに
2
を指定 - backgroundに、バックグラウンドで動作する
background.js
を指定 (background.js の中で、アプリで使うhtmlを指定している) - permissionsに、
serial
を指定
といったところです。
{ "name": "chrome serial example", "description": "chrome serial example", "version": "1", "manifest_version": 2, "app": { "background": { "scripts": ["background.js"] } }, "icons": { "128": "128x128.png", "16": "16x16.png" }, "permissions": [ "serial" ] }
background.js
chrome.app.window.create()
メソッドで、表示する画面のhtmlを指定します。また、必要に応じて画面のサイズや表示位置も指定します。
chrome.app.runtime.onLaunched.addListener(function(){ chrome.app.window.create('window.html', { 'bounds': { 'width': 250, 'height': 200, 'top': 0, 'left': 100 } }); });
window.html
表示する画面と処理を実行するJavaScriptを指定します。今回は、
- シリアルCOMポートを選択する、select menu (id="port")
- シリアルCOMポートに接続・データ受信をするためのボタン (id ="connect")
- 全シリアルCOMポートの表示領域 (id="list")
- 実際に接続・データ受信処理を実行するJavaScript (serial.js)
を記載しています。
<select id="port"></select> <button id="connect">connect</button> <div> <ul id="list"> </ul> </div> <script src="serial.js"></script>
serial.js
メインの処理を記述しています。
なお、window.alert()
を使おうとすると「window.alert() is not available in packaged apps.」というエラーが発生したため、実行結果などは console.log
でコンソールへと出力しています。
Using the Console - Google Chrome
COMポートの列挙と追加
ロード時に現在の端末にあるCOMポートを列挙し、画面とselect menu に追加します。今のところ、手元の環境ではport.displayName
が設定されませんでした。
また、以下のようなBlog記事があったため、loadイベントの設定ではwindow.addEventListener()
を使いました。
Loox Uと初音ミクで行こう!: Google ChromeでDOMContentLoadedが発生しないケースがある
var loadedListener = function() { console.log('loaded'); // デバイスをリスト化して、画面に表示する chrome.serial.getDevices(function(devices) { var list = document.getElementById('list'); var selection = document.getElementById('port'); devices.forEach(function(port){ // 画面に表示 var li = document.createElement('li'); li.textContent = port.path; list.appendChild(li); // select menu に追加 var option = document.createElement('option'); option.value = port.path; // 今のところ、手元の環境ではport.displayNameが設定されていない option.text = port.displayName ? port.displayName : port.path; selection.appendChild(option); }); }); }; window.addEventListener('load', loadedListener, false);
COMポートへの接続
ボタンを押した時にCOMポートへ接続するようにするコードです。
なお、onReceiveイベントで使用するため、接続時の connectionId
はこのイベントで取得したものを保持しておきます。
また、receiveTimeout
を設定しておくことで onReceiveError
イベントが発生することを利用し、データ受信の完了を知らせるようにしています。
var onConnectCallback = function(connectionInfo){ // onReceiveイベントでconnectionIdの一致を確認するので、保持しておく connectionId = connectionInfo.connectionId; } var clickedListener = function() { console.log('clicked'); var e = document.getElementById('port'); var port = e.options[e.selectedIndex].value; chrome.serial.connect(port, {bitrate: 9600, receiveTimeout: 5000}, onConnectCallback); } document.getElementById('connect').addEventListener("click", clickedListener, false);
シリアルCOMポートからデータを受信
公式のサンプルを参考に、データを受信する部分を記載します。
Serial Devices - Google Chrome
なお、公式のサンプルには expectedConnectionId
や convertArrayBufferToString()
の定義は見つけられませんでした。
そこで、前者は接続時に保持しておいたconnectionInfo.connectionId
、後者は公式のサンプルにあった関数のことだろうと判断し、コードを書きました。
chrome-app-samples/serial/ledtoggle/main.js | GoogleChrome/chrome-app-samples
/* Interprets an ArrayBuffer as UTF-8 encoded string data. */ function convertArrayBufferToString(buf){ var bufView = new Uint8Array(buf); var encodedString = String.fromCharCode.apply(null, bufView); return decodeURIComponent(escape(encodedString)); } var onReceiveCallback = function(info) { console.log('received'); if (info.connectionId == connectionId && info.data) { // こいつがうまく動かない:メソッドを定義してあげるとよい var str = convertArrayBufferToString(info.data); // 改行コード来たら、一つのデータの終端 if (str.charAt(str.length-1) === '\n') { arrayReceived.push(stringReceived) stringReceived = ''; } else { stringReceived += str; } } }; chrome.serial.onReceive.addListener(onReceiveCallback);
受信したデータのコンソールへの表示
接続時に receiveTimeout
を設定しているため、全件受信した後にしばらく待つと onReceiveError
イベントが発生します。そのタイミングで、受信したデータの配列をコンソールへと表示しています。
また、データ受信が完了した時は接続を切るようにします。
var onDisconnectCallback = function(result) { if (result) { console.log('disconnected'); } else { console.log('error'); } } var onReceiveErrorCallback = function(info) { console.log('end'); // receiveTimeoutで設定した時間が経過すると、このイベントが発生 // ->データの受信は全て完了したとみなし、受信データを一覧表示する console.log(arrayReceived.join(',')); var disconnect = chrome.serial.disconnect(connectionId, onDisconnectCallback) } chrome.serial.onReceiveError.addListener(onReceiveErrorCallback);
画面イメージとコンソール出力
画面の枠を作っていないので分かりにくいですが、画面イメージです。
コンソールへの表示はこちら。
ソースコード全体
GitHubに上げておきました。
ChromeApps-sample/serial at master · thinkAmi/ChromeApps-sample
参考
API関連
- GoogleChrome/chrome-app-samples ? GitHub
- JavaScript APIs - Google Chrome
- ある日chrome.serial APIを使用したchrome Appが動作しなくなっていた件
- Create Your First App - Google Chrome
- Chrome Packaged Apps コードラボ 【Chrome本出版記念!】に参加してきた | Developers.IO