Mbed OS version of IoT.js implementation running on GR-PEACH

ビルド方法

ビルド環境セットアップ

Host PC要件 : Ubuntu 16.04 (64-bit)



1. 以下のコマンドを実行し、下表に示すツールをインストールします。

$ sudo apt update
$ sudo apt upgrade
$ sudo apt install [Package]
PackageVersion
build-essential12.1ubuntu2
gyp0.1+20150913git1f374df9-1ubuntu1
mercurial3.7.3-1ubuntu1
cmake3.5.1-1ubuntu3
git1:2.7.4-0ubuntu1.4
python2.72.7.12-1ubuntu016.04.3
valgrind1:3.11.0-1ubuntu4.2
python-pip8.1.1-2ubuntu0.4

2. 以下のコマンドを実行してMbed OS用ビルドツール mbed CLIをインストールします。

$ sudo -H pip install mbed-cli

3. GNU Arm Embedded Toolchainをインストールします。

  • ダウンロードしたファイルを任意のディレクトリに展開します。
    (以下、展開先を${TOOLCHAIN}と記載します)

tar xjf gcc-arm-none-eabi-6-2017-q2-update-linux.tar.bz2
  • ツールチェインのパスを設定します。

環境変数を使用する場合

$ export GCC_ARM_ROOT=${TOOLCHAIN}/gcc-arm-none-eabi-6-2017-q2-update/bin
$ export PATH=$GCC_ARM_ROOT:$PATH

mbed CLIで指定する場合

$ mbed config -G GCC_ARM_PATH $GCC_ARM_ROOT

iotjs環境のビルド

1. 本プログラムのクローン

下記コマンドを実行し、本プログラムをクローンしてください。

$ hg clone https://HinoNaka@os.mbed.com/users/HinoNaka/code/GR-PEACH_mbed-os-iotjs/

クローンが正常に終了すると、GR-PEACH_mbed-os-iotjs という名称のディレクトリが生成されますので、下記コマンドで当該ディレクトリへ移動してください。

$ cd GR-PEACH_mbed-os-iotjs

(Optional)
GR-PEACH-mbed-os-iotjsディレクトリを指す環境変数 ROOTを設定してください。以降の記載は本環境変数が設定されているものとします。

$ export ROOT=$(PWD)

2. 作業用ディレクトリ(e.g. work)を作成し、当該ディレクトリに移動してください。

$ mkdir -p work
$ cd work

(Optional)
作業用ディレクトリを指す環境変数 WORK を設定してください。以降の記載は本環境変数 が設定されているものとします。

$ export WORK=$(PWD)

3. iotjsのソースツリーを取得します。

$ git clone https://github.com/pando-project/iotjs

クローンが正常に終了するとiotjsというディレクトリが生成されますので、当該ディレクトリに移動してください。

$cd iotjs

コミットID: acae9c8b2d40e7598b8d39b630b79113ce880a7e を取得します。
(下記例では、あわせてposixというブランチを作成し、当該ブランチへ切り替えています)

$ git checkout -b posix acae9c8b2d40e7598b8d39b630b79113ce880a7e

4. mbed-osセットアップ
Mbed OSソースツリーを格納するフォルダを生成し、当該フォルダへ移動します。

$ mkdir -p $(WORK)/iotjs/src/platform/mbedos5
$ cd $(WORK)/iotjs/src/platform/mbedos5

Mbed OSのソースツリーをクローンします。

$ git clone -b mbed-os-5.9 https://github.com/ARMmbed/mbed-os.git

クローンが正常に終了するとmbed-osというディレクトリが生成されますので、当該ディレクトリに移動してください。

$ cd mbed-os

コミットID: 50bd61a4a72332baa6b1bac6caccb44dc5423309 を取得します。
(下記例では、あわせてposixというブランチを作成し、当該ブランチへ切り替えています)

$ git checkout -b posix 50bd61a4a72332baa6b1bac6caccb44dc5423309

5. sd-driverの取得
下記コマンドでmbedos5ディレクトリへ移動します。

$ cd $(WORK)/iotjs/src/platform/mbedos5

sd-driverをクローンします。

$ git clone https://github.com/ARMmbed/sd-driver.git

クローンが正常終了するとsd-driverディレクトリが生成されますので、当該ディレクトリへ移動します。

$ cd sd-driver

コミットID: c8ae38fb291e086232566b0f1372cfb69c277e84 を取得します。
(下記例では、あわせてposixというブランチを作成し、当該ブランチへ切り替えています)

$ git checkout -b posix c8ae38fb291e086232566b0f1372cfb69c277e84

6. mbed-gr-libsの取得
下記コマンドでmbedos5ディレクトリへ移動します。

$ cd $(WORK)/iotjs/src/platform/mbedos5

mbed-gr-libsをクローンします。

$ git clone https://github.com/d-kato/mbed-gr-libs

クローンが正常終了するとmbed-gr-libsディレクトリが生成されますので、当該ディレクトリへ移動します。

$ cd mbed-gr-libs

コミットID: d921d611d596ecaebaab49070ef82450c583309c を取得します。
(下記例では、あわせてposixというブランチを作成し、当該ブランチへ切り替えています)

$ git checkout -b posix d921d611d596ecaebaab49070ef82450c583309c

7. AsciiFONTの取得
下記コマンドでmbedos5ディレクトリへ移動します。

$ cd $(WORK)/iotjs/src/platform/mbedos5

AsciiFONTライブラリを追加します。

$ mbed add https://os.mbed.com/teams/Renesas/code/AsciiFont/

8. iotjs環境のビルド
下記コマンドでiotjs環境のビルドディレクトリに移動し、ビルドを実行します。

$ cd $(WORK)/iotjs
$ ./tools/build.py --no-snapshot

9. GR-PEACH用コードのコピー
(Optional) 既存のiotjs環境に上書きする場合、下記コマンドを実行してください。

$ rm -rf $(WORK)/iotjs/deps/posix
$ rm -rf $(WORK)/iotjs/src/ext-modules
$ rm -rf $(WORK)/iotjs/src/platform/mbedos5/iotjs_def.h

下記コマンドでGR-PEACH用コードをコピーします。

$ cp -pr $(ROOT)/src/iotjs/* $(WORK)/iotjs/

10. lwipパッチ適用
以下コマンドでlwipパッチを適用します。

$ cd $(WORK)/iotjs/src/platform/mbedos5/mbed-os
$ git apply lwip_improve.patch

11. ESP32 TRNG対応パッチ適用
以下コマンドでESP32のTRNGを活用するためのパッチを適用します。

$ cd $(WORK)/iotjs/src/platform/mbedos5/mbed-os
$ git apply trng_support.patch

12. Mbed OS版iotjs環境のビルド
下記コマンドを実行してMbed OS版iotjs環境をビルドします。
(リリースビルドの場合)

$ cd $(WORK)/iotjs/src/platform/mbedos5
$ make clean; make DEBUG=0

(デバッグビルドの場合)

$ cd $(WORK)/iotjs/src/platform/mbedos5
$ make clean; make DEBUG=1

13. ビルドが正常に終了すると、以下の通りバイナリが生成されます。
(リリースビルドの場合)

$ ls -l ${WORK}/iotjs/src/platform/mbedos5/BUILD/RZ_A1H/GCC_ARM-RELEASE/iotjs.bin

(デバッグビルドの場合)

$ ls -l ${WORK}/iotjs/src/platform/mbedos5/BUILD/RZ_A1H/GCC_ARM-DEBUG/iotjs.bin

実行方法

1. GR-PEACHにmicro SDカードを挿入します。

2. GR-PEACHのEthernetポートから遠い側のUSBポートとHost PCをマイクロUSBケーブルで接続します。

3. Host PCでGR-PEACHが『mbed:』ドライブとして認識されたら、iotjs.binをmbedドライブにコピーします。

4. ターミナルS/Wを立上げ、『mbed Serial Port』と接続します。
/media/uploads/HinoNaka/img1.jpg
シリアルポートは下記設定としてください。

Baud rate115200
Data8 bit
Paritynone
Stop1 bit
Flow controlnone

5. 正常に書込みが終了したらGR-PEACHのRESETボタンを押下します。

6. ターミナルS/W上に下記メッセージが表示されます。
/media/uploads/HinoNaka/img2.jpg

7. Enterキーを押下してコード入力モードに移行します。
/media/uploads/HinoNaka/img3.jpg

8. 実行するJavascriptコードをターミナルソフトウェアに貼り付けてEnterキーを押下すると、実行開始します。Javascriptコード例については、test/case.txtを参照ください。

Revision:
1:c3d69f309845
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/case.txt	Thu Jul 11 18:49:37 2019 +0900
@@ -0,0 +1,1122 @@
+■テストケースについて
+
+    次の行で囲まれた部分がそれぞれのテストケースになります。
+    //boc ---------- (BeginOfCode)
+    //eoc ---------- (EndOfCode)
+
+・使用例)
+
+1)下記の //boc の *前行* から //eoc の *次行* までをクリップボードにコピーしておきます。
+
+//boc ---------- (BeginOfCode)
+console.log("001");
+//eoc ---------- (EndOfCode)
+
+2)GR-Peach をリセット(or BREAK信号を送信)します。
+    ※ GR-Peach には *必ず* microSD カードをセットしておいてください。
+
+    IoT.js for mbed-os...
+       mbed-os version: 5.9.3 
+       build timestamp: MMM DD YYYY HH:MM:SS 
+
+    input js fullpath here: 
+
+3)[Enter]キーを送信して、コードの入力モードに移行します。
+
+    input javascript code here: 
+
+4)クリップボードの内容を貼り付けると処理が始まります。(手入力でもOK)
+
+    input javascript code here: 
+    //boc ---------- (BeginOfCode)
+    console.log("001");
+    //eoc ---------- (EndOfCode)
+
+    ------- POSIX[  1][thr0] -->clock_getres(clk_id=6, res=0x2005aae4)
+    ------- POSIX[  2][thr0] <--clock_getres(clk_id=6, res={0.001000000}) = 0(errno:0)
+    ------- POSIX[  3][thr0] -->clock_gettime(clk_id=6, tp=0x2005aae4)
+                :
+                :
+
+
+
+■いくつかのテストケース
+
+
+
+//boc ---------- (BeginOfCode)
+//■ process.platform が "mbedos" か確認
+console.log( 'platform = ' + process.platform );
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ process 環境表示テスト
+console.log( 'platform' + JSON.stringify( process, null, "    " ) );
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ カレントパスのテスト
+console.log('current path: "' + process.cwd() + '"');
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ console 表示テスト (UTF8)
+console.log("001");
+console.log("002", "002");
+var val = "string";
+console.log("003", val);
+val = 123;
+console.log("004", val);
+val = 3.14;
+console.log("005", val);
+console.log("nihongo", "UTF8");
+console.log("nihongo", "日本語", "UTF8");
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ JSON.stringify、JSON.parse のテスト
+var obj = {abc:10, pi:3.14, str:"moji"};
+var json = JSON.stringify(obj);
+console.log( json );
+var copy = JSON.parse(json);
+console.log( copy );
+console.log( JSON.stringify(copy) );
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ chdir のテスト
+function CHECK( number, val, must_be)
+{
+    console.log( 'check', number, ': must be', must_be, ':', val == must_be ? 'OK' : 'NG' );
+}
+
+var currentPath = process.cwd();
+console.log('currentPath', process.cwd());
+
+try {
+    process.chdir('/sd');
+} catch(err) {
+    console.log('invalid path');
+}
+
+console.log('currentPath', process.cwd());
+CHECK(1, process.cwd(), '/sd');
+
+process.chdir(currentPath);
+console.log('currentPath', process.cwd());
+
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ Buffer のテスト (linux版と結果が同じであること)
+var buf = new Buffer('hello world', 'ascii');
+console.log(buf.toString('hex'));     // Prints: 68656c6c6f20776f726c64
+console.log(buf.toString('base64'));  // Prints: aGVsbG8gd29ybGQ=
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ setTimeout() のテスト
+setTimeout( function() {
+    console.log( new Date().getTime(), 'kita' );
+}, 3000);
+console.log( new Date().getTime(), 'setTimeout started');
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ setInterval() のテスト
+var count = 0;
+var tid = setInterval( function() {
+    console.log( new Date().getTime(), 'setInterval kita' );
+    if( ++ count >= 5 ) clearInterval( tid );
+}, 1000);
+console.log( new Date().getTime(), 'setInterval started');
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ setTimeout(), setInterval() のテスト
+setTimeout( function() {
+    console.log( new Date().getTime(), 'setTimeout1 kita' );
+}, 3500);
+console.log( new Date(), 'setTimeout1 started');
+
+setTimeout( function() {
+    console.log( new Date().getTime(), 'setTimeout2 kita' );
+}, 5500);
+console.log( new Date().getTime(), 'setTimeout2 started');
+
+var count = 0;
+var tid = setInterval( function() {
+    console.log( new Date().getTime(), 'setInterval kita' );
+    if( ++ count >= 5 ) clearInterval( tid );
+}, 2000);
+console.log( new Date().getTime(), 'setInterval started');
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ fs モジュールのテスト (同期)
+var fs = require('fs');
+var fname = "/sd/_temp_code.js";
+var stat = fs.statSync(fname);
+console.log( JSON.stringify(stat) );
+console.log( fs.readFileSync(fname) );
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ 正規表現のテスト
+var re = /(\w+)\s(\w+)/;
+var str = 'John Smith';
+var newstr = str.replace(re, '$2, $1');
+console.log(newstr);
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ 文字列分割テスト
+var text = 'Some text\nAnd some more\r\nAnd yet\rThis is the end';
+var lines = text.split(/\r\n|\r|\n/);
+console.log(lines); // logs [ 'Some text', 'And some more', 'And yet', 'This is the end' ]
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ 多国語テスト
+var text = 'Образец text на русском языке';
+var regex = /[\u0400-\u04FF]+/g;
+
+var match = regex.exec(text);
+console.log(match[0]);        // logs 'Образец'
+console.log(regex.lastIndex); // logs '7'
+
+var match2 = regex.exec(text);
+console.log(match2[0]);       // logs 'на' [did not log 'text']
+console.log(regex.lastIndex); // logs '15'
+
+// and so on
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+//■ 文字列操作テスト
+var url = 'http://xxx.domain.com';
+console.log(/[^.]+/.exec(url)[0].substr(7)); // logs 'xxx'
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■EventEmitterテスト
+var EventEmitter = require('events').EventEmitter;
+
+function asyncFunc() {
+  var ev = new EventEmitter;
+  console.log('in asyncFunc');
+  setTimeout(function () {
+    ev.emit('done', 'foo', 'bar');
+  }, 1000);
+  return ev;
+}
+
+var async = asyncFunc();
+async.on('done', function(arg1, arg2) {
+  console.log(arg1, arg2);
+});
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■streamテスト
+
+var Readable = require('stream').Readable;
+
+function CHECK( number, val, must_be)
+{
+    console.log( 'check', number, ': must be', must_be, ':', val == must_be ? 'OK' : 'NG' );
+}
+
+var readable1 = new Readable();
+var d = "";
+var e = "";
+
+
+readable1.on('error', function(err) {
+  e += ".";
+});
+
+readable1.on('data', function(data) {
+  d += data.toString();
+});
+
+readable1.on('end', function() {
+  e += 'e';
+});
+
+
+readable1.pause();
+readable1.push('abcde');
+readable1.push('12345');
+CHECK(1, d, '');
+CHECK(2, e, '');
+
+readable1.resume();
+CHECK(3, d, 'abcde12345');
+CHECK(4, e, '');
+
+readable1.push('a');
+readable1.push('1');
+readable1.push('b');
+readable1.push('2');
+CHECK(5, d, 'abcde12345a1b2');
+CHECK(6, e, '');
+
+CHECK(7, readable1.isPaused(), false);
+readable1.pause();
+CHECK(8, d, 'abcde12345a1b2');
+CHECK(9, e, '');
+CHECK(10, readable1.isPaused(), true);
+
+// Pause the readable again. This should do nothing.
+readable1.pause();
+CHECK(11, readable1.isPaused(), true);
+
+readable1.push('c');
+readable1.push('3');
+readable1.push('d');
+readable1.push('4');
+CHECK(12, d, 'abcde12345a1b2');
+CHECK(13, e, '');
+
+readable1.resume();
+CHECK(14, d, 'abcde12345a1b2c3d4');
+CHECK(15, e, '');
+
+readable1.push(null);
+CHECK(16, d, 'abcde12345a1b2c3d4');
+CHECK(17, e, 'e');
+
+readable1.push('push after eof');
+CHECK(18, d, 'abcde12345a1b2c3d4');
+CHECK(19, e, 'e.');
+
+
+// Create a readable stream without the new keyword.
+var readable2 = Readable({encoding: 'utf8'});
+
+// Read with irregular parameters from an empty stream.
+CHECK(20, readable2.read(-2), null);
+CHECK(21, readable2.read(0), null);
+
+readable2.push('qwerty');
+CHECK(22, readable2.read(6), 'qwerty');
+
+// Throw not implemented Error when we trying to read less length data.
+readable2.push('new-data');
+
+var readable3 = new Readable();
+var readable3End = false;
+var paused = false;
+var str = 'test';
+
+readable3.on('data', function(data) {
+  CHECK(23, paused, true);
+  CHECK(24, data, str);
+});
+
+readable3.on('end', function() {
+  readable3End = true;
+});
+
+readable3.pause();
+readable3.push(str);
+readable3.push(null);
+
+setTimeout(function() {
+  paused = true;
+  readable3.resume();
+}, 1000);
+
+
+process.on('exit', function() {
+  CHECK(25, readable2 instanceof Readable, true);
+  CHECK(26, readable3End, true);
+});
+
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■ dns モジュールの簡易テスト
+var dns = require('dns');
+dns.lookup('www.google.co.jp', function(err, address, family) {
+    console.log('www.google.co.jp -> address: %j family: IPv%s', address, family);
+});
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■ nic モジュールの簡易テスト
+var nic = require('nic');
+// 一覧取得
+var nics = nic.enumerate();
+// 一覧確認
+console.log( Object.keys( nics ).length + "個のNICが搭載されています" );
+var index = 0;
+Object.keys( nics ).forEach( function(key) {
+    console.log( ++ index + "個目は:" + key );
+});
+// 使い方
+var eth0 = nics["ETHERNET"];
+if( eth0 ) {
+    eth0.ifup();        // use DHCP
+    eth0.ntpdate({ server:"ntp.nict.jp" });
+    console.log( JSON.stringify( eth0.ifconfig() ) );
+    console.log( new Date().toString() );
+}
+var wifi = nics["WIFI_BP3595"];
+if( wifi ) {
+    wifi.ifup( {
+        wifi: {
+            ssid: "<SSID>",
+            password: "<PASSWORD>",
+            security: "WPA_WPA2"    // 候補:WEP, WPA, WPA2, WPA_WPA2
+        },
+        ip: "192.168.10.123",
+        netmask: "255.255.255.0",
+        gateway: "192.168.10.1",
+        dns: "8.8.8.8"
+    });
+    wifi.ntpdate();
+    console.log( JSON.stringify( wifi.ifconfig() ) );
+    console.log( new Date().toString() );
+}
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■ http クライアントのテスト
+
+var cli = require('http');
+require('fixup')('http');
+cli.get({ host: 'www.google.com' }, function(res) {
+	console.log( 'res.statusCode', res.statusCode);
+	res.on('data', function(chunk) {
+		console.log( chunk.toString() );
+	});
+	res.on('end', function() {
+		console.log( 'end' );
+	});
+});
+
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■ https クライアントのテスト
+
+var cli = require('https');
+require('fixup')('https');
+cli.get({ host: 'www.google.com' }, function(res) {
+	console.log( 'res.statusCode', res.statusCode);
+	res.on('data', function(chunk) {
+		console.log( chunk.toString() );
+	});
+	res.on('end', function() {
+		console.log( 'end' );
+	});
+});
+
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■ カメラ入力をJPEGファイルに保存する
+var video = require('video');
+var jpeg = require('jpeg');
+var fs = require('fs');
+var AlignedBuffer = require('aligned_buffer').AlignedBuffer;
+
+var width = 480;
+var height = 272;
+var video_format = 'ycbcr422';
+var pixel_bytes = 2;
+var alignment = 32;
+var video_buf = new AlignedBuffer(width * height * pixel_bytes, alignment);
+
+var camera_config = {
+    width : width,
+    height : height,
+    format : video_format,
+    type : 'ov7725'
+};
+var jpeg_config = {
+    width : width,
+    height : height,
+    format : video_format
+};
+
+var interval = 1*1000; // 画像保存周期(ms)
+var count = 10; // 保存する画像ファイル数
+
+// カメラの初期化
+video.openCMOSCamera(camera_config, function(err, video_source){
+    if(err) {
+        console.log(err);
+        return;
+    }
+    // 映像の取り込み開始
+    video_source.start(video_buf, function(err) {
+        if(err) {
+            console.log(err);
+            return;
+        }
+        // interval の周期で count 数のJPEG画像を保存
+        var i = 0;
+        var timer = setInterval(function() {
+            if(i >= count) {
+                clearInterval(timer);
+                video_source.stopSync(); // 映像の取り込み停止
+                video_source.closeSync(); // カメラリソースの解放
+                return;
+            }
+            jpeg_config.bitmap = new AlignedBuffer(video_buf, alignment);
+            var jpeg_data = jpeg.encodeSync(jpeg_config);
+            fs.writeFileSync('/sd/image' + i + '.jpg', jpeg_data.toBuffer());
+            console.log('/sd/image' + i + '.jpg');
+            i++;
+        }, interval);
+    });
+});
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■ LCDのグラフィックス描画テスト
+var display = require('display');
+var Graphics = require('graphics').Graphics;
+var AlignedBuffer = require('aligned_buffer').AlignedBuffer;
+
+var width = 480;
+var height = 272;
+var pixel_bytes = 2;
+var alignment = 32;
+
+var buf_size = width * height * pixel_bytes;
+
+var lcd_config = {
+    type : '4.3inch'
+};
+
+// レイヤー0用フレームバッファ初期化
+var format0 = 'rgb565';
+var buf0 = new AlignedBuffer(buf_size, alignment);
+var graphics0 = new Graphics({buf:buf0, width:width, height:height, format:format0});
+graphics0.drawRect(0,0,width,height,0xFFFF,true); // 全領域を白で塗りつぶし
+
+// レイヤー1用フレームバッファ初期化
+var format1 = 'argb4444';
+var buf1 = new AlignedBuffer(buf_size, alignment);
+var graphics1 = new Graphics({buf:buf1, width:width, height:height, format:format1});
+graphics1.drawRect(0,0,width,height,0x0000,true); // 全領域を完全透過
+
+var interval = 1*1000; // LCD表示切り替え周期(ms)
+
+// LCDの初期化
+display.openLCD(lcd_config, function(err, lcd) {
+    if(err) {
+        console.log(err);
+        return;
+    }
+
+    var i = 0;
+    var timer = setInterval(function(){
+        switch(i) {
+        case 0:
+            // レイヤー0の表示を開始(白背景)
+            lcd.startSync(0, graphics0.frameBuffer, format0);
+            break;
+        case 1:
+            // レイヤー1の表示を開始(白背景に赤の円)
+            graphics1.drawCircle(100,100,50,0xFF00,true);
+            lcd.startSync(1, graphics1.frameBuffer, format1);
+            break;
+        case 2:
+            // レイヤー0のフレームバッファを切り替え(青背景に赤の円)
+            var new_buf0 = new AlignedBuffer(buf_size, alignment);
+            var new_graphics0 = new Graphics({buf:new_buf0, width:width, height:height, format:format0});
+            new_graphics0.drawRect(0,0,width,height,0x001F,true); // 全領域を青で塗りつぶし
+            lcd.updateSync(0, new_graphics0.frameBuffer);
+            break;
+        case 3:
+            // レイヤー0の表示を停止(黒背景に赤の円)
+            lcd.stopSync(0);
+            break;
+        case 4:
+            // レイヤー1の表示を停止(黒背景)
+            lcd.stopSync(1);
+            break;
+        default:
+            // LCDリソースの解放
+            lcd.closeSync();
+            clearInterval(timer);
+            break;
+        }
+        i++;
+    }, interval);
+});
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■ カメラ入力をGoogle Vision APIに送信し、検出結果をLCDに描画するテスト
+var display = require('display');
+var video = require('video');
+var jpeg = require('jpeg');
+var https = require('https');
+var AlignedBuffer = require('aligned_buffer').AlignedBuffer;
+var Graphics = require('graphics').Graphics;
+require('fixup')('https');
+
+var width = 480;
+var height = 272;
+var video_format = 'ycbcr422';
+var pixel_bytes = 2;
+var alignment = 32;
+var video_buf = new AlignedBuffer(width * height * pixel_bytes, alignment);
+
+var result_format = 'argb4444';
+var result_color = 0xF00F;
+var result_pixel_bytes = 2;
+
+var lcd_config = {
+    type : '4.3inch'
+};
+var camera_config = {
+    width : width,
+    height : height,
+    format : video_format,
+    type : 'ov7725'
+};
+var jpeg_config = {
+    width : width,
+    height : height,
+    format : video_format
+};
+
+var key = 'key=<your APIKey>';
+var timeout = 10*1000;
+var detection_type = 'FACE_DETECTION';
+//var detection_type = 'OBJECT_LOCALIZATION';
+
+var lcd;
+display.openLCD(lcd_config, function(err, lcd_) {
+    console.log('LCD Open Callback');
+
+    if(!err) {
+        lcd = lcd_;
+        lcd.start(0, video_buf, video_format, function(err){
+            if(!err) console.log('LCD Layer 0 Start');
+            else     console.log(err);
+        });
+
+        console.log('Camera Open Start');
+        video.openCMOSCamera(camera_config, function(err, video_source){
+            if(!err) {
+                video_source.start(video_buf, function(){
+                    console.log('Camera Start');
+
+                    // 10秒後にJPEGエンコード開始
+                    setTimeout(function() {
+                        console.log('JPEG Encode Start');
+                        video_source.stopSync();
+                        jpeg_config.bitmap = new AlignedBuffer(video_buf, alignment);
+                        jpeg.encode(jpeg_config, function(err, data) {
+                            console.log('JPEG Encode Callback');
+                            if(!err && data) {
+                                use_google_vision_api(data.toBuffer().toString('base64'));
+                            } else {
+                                console.log(err);
+                            }
+                        });
+                    }, timeout);
+                });
+            }
+            else {
+                console.log(err);
+            }
+        });
+    }
+    else {
+        console.log(err);
+    }
+});
+
+console.log('LCD Open Start');
+
+function use_google_vision_api(image) {
+    var host = 'vision.googleapis.com';
+    var port = 443;
+    var path = '/v1/images:annotate';
+
+    var data = '{"requests":[{"image":{"content":"'
+            + image 
+            + '"},"features":[{"type":"' + detection_type + '"}]},]}';
+
+    var options = {
+        host: host,
+        port: port,
+        path: path + '?' + key,
+        method: 'POST',
+        headers: {
+            'Host': host + ':' + port,
+            'Content-Type': 'application/json',
+            'Content-Length': data.length
+        }
+    };
+
+    var req = https.request(options, function(res) {
+        var body = '';
+        console.log( 'res.statusCode', res.statusCode);
+
+        res.on('data',function(chunk) {
+            body += chunk;
+        });
+        res.on('end',function() {
+            console.log(body);
+            drawResult(JSON.parse(body));
+        });
+    });
+    req.on( 'error', function( e ) {
+        console.log( 'error' );
+        console.log( e.message );
+    });
+
+    req.writeEntire = function(data, block_size) {
+        block_size = block_size || 1024;
+        for(var i = 0; i < data.length; i += block_size) {
+            this.write(data.slice(i, i + block_size));
+        }
+    };
+    req.writeEntire(data);
+    req.end();
+}
+
+function drawResult(res) {
+    switch(detection_type) {
+    case 'FACE_DETECTION':
+        drawFace(res);
+        break;
+    case 'OBJECT_LOCALIZATION':
+        drawObject(res);
+        break;
+    default:
+        break;
+    }
+}
+
+function drawObject(res) {
+    var result_buf = new AlignedBuffer(width*height*result_pixel_bytes, alignment);
+    var result = new Graphics({buf:result_buf, width:width, height:height, format:result_format});
+    result.drawRect(0, 0, width, height, 0x0000, true);
+
+    // response format
+    // https://cloud.google.com/vision/docs/reference/rest/v1/images/annotate?hl=ja#LocalizedObjectAnnotation
+    var objects = res.responses[0].localizedObjectAnnotations;
+    objects.forEach(function(e, index){
+        var rect = e.boundingPoly.normalizedVertices;
+        rect.forEach(function(e) {
+            e.x *= width;
+            e.y *= height;
+        });
+        result.drawRect(rect[0].x,
+                   rect[0].y,
+                   rect[1].x - rect[0].x,
+                   rect[3].y - rect[0].y,
+                   result_color, false);
+        result.drawText(e.name, rect[0].x + 1, rect[0].y + 1, 1, result_color, 0x0000);
+    });
+
+    lcd.startSync(2, result.frameBuffer, result_format);
+}
+
+function drawFace(res) {
+    var result_buf = new AlignedBuffer(width*height*result_pixel_bytes, alignment);
+    var result = new Graphics({buf:result_buf, width:width, height:height, format:result_format});
+    result.drawRect(0, 0, width, height, 0x0000, true);
+
+    var background_buf = new AlignedBuffer(width*height*result_pixel_bytes, alignment);
+    var background_format = 'rgb565';
+    var background = new Graphics({buf : background_buf, width:width, height:height, format:background_format});
+    background.drawRect(0, 0, width, height, 0xFFFF, true);
+
+    result.drawPolyline = function ( positions, close, color ) {
+        for(var i = 0; i < positions.length - 1; i++) {
+            this.drawLine(positions[i].x,
+                              positions[i].y,
+                              positions[i+1].x,
+                              positions[i+1].y,
+                              color);
+        }
+        if(close) {
+            this.drawLine(positions[positions.length-1].x,
+                              positions[positions.length-1].y,
+                              positions[0].x,
+                              positions[0].y,
+                              color);
+        }
+    };
+
+    // response format
+    // https://cloud.google.com/vision/docs/reference/rest/v1/images/annotate?hl=ja#FaceAnnotation
+
+    res.responses[0].faceAnnotations.forEach(function(faceAnnotation){
+
+        var landmarks = faceAnnotation.landmarks;
+        landmarks.findIndex = function(type) {
+            var ret;
+            this.forEach(function(e, index) {
+                if(e.type == type) ret = index;
+            });
+            return ret;
+        };
+
+        var outline = [];
+        outline.push(landmarks[landmarks.findIndex('LEFT_EAR_TRAGION')].position);
+        outline.push(landmarks[landmarks.findIndex('CHIN_LEFT_GONION')].position);
+        outline.push(landmarks[landmarks.findIndex('CHIN_GNATHION')].position);
+        outline.push(landmarks[landmarks.findIndex('CHIN_RIGHT_GONION')].position);
+        outline.push(landmarks[landmarks.findIndex('RIGHT_EAR_TRAGION')].position);
+        result.drawPolyline(outline, false, result_color);
+
+        var left_eyeblow = [];
+        left_eyeblow.push(landmarks[landmarks.findIndex('LEFT_OF_LEFT_EYEBROW')].position);
+        left_eyeblow.push(landmarks[landmarks.findIndex('LEFT_EYEBROW_UPPER_MIDPOINT')].position);
+        left_eyeblow.push(landmarks[landmarks.findIndex('RIGHT_OF_LEFT_EYEBROW')].position);
+        result.drawPolyline(left_eyeblow, false, result_color);
+
+        var right_eyeblow = [];
+        right_eyeblow.push(landmarks[landmarks.findIndex('LEFT_OF_RIGHT_EYEBROW')].position);
+        right_eyeblow.push(landmarks[landmarks.findIndex('RIGHT_EYEBROW_UPPER_MIDPOINT')].position);
+        right_eyeblow.push(landmarks[landmarks.findIndex('RIGHT_OF_RIGHT_EYEBROW')].position);
+        result.drawPolyline(right_eyeblow, false, result_color);
+
+        var left_eye = [];
+        left_eye.push(landmarks[landmarks.findIndex('LEFT_EYE_TOP_BOUNDARY')].position);
+        left_eye.push(landmarks[landmarks.findIndex('LEFT_EYE_RIGHT_CORNER')].position);
+        left_eye.push(landmarks[landmarks.findIndex('LEFT_EYE_BOTTOM_BOUNDARY')].position);
+        left_eye.push(landmarks[landmarks.findIndex('LEFT_EYE_LEFT_CORNER')].position);
+        result.drawPolyline(left_eye, true, result_color);
+
+        var eye = landmarks[landmarks.findIndex('LEFT_EYE')].position
+        result.drawCircle(eye.x, eye.y, 2, result_color, true);
+        eye = landmarks[landmarks.findIndex('RIGHT_EYE')].position
+        result.drawCircle(eye.x, eye.y, 2, result_color, true);
+
+        var right_eye = [];
+        right_eye.push(landmarks[landmarks.findIndex('RIGHT_EYE_TOP_BOUNDARY')].position);
+        right_eye.push(landmarks[landmarks.findIndex('RIGHT_EYE_RIGHT_CORNER')].position);
+        right_eye.push(landmarks[landmarks.findIndex('RIGHT_EYE_BOTTOM_BOUNDARY')].position);
+        right_eye.push(landmarks[landmarks.findIndex('RIGHT_EYE_LEFT_CORNER')].position);
+        result.drawPolyline(right_eye, true, result_color);
+
+        var nose = [];
+        nose.push(landmarks[landmarks.findIndex('NOSE_BOTTOM_LEFT')].position);
+        nose.push(landmarks[landmarks.findIndex('NOSE_TIP')].position);
+        nose.push(landmarks[landmarks.findIndex('NOSE_BOTTOM_RIGHT')].position);
+        nose.push(landmarks[landmarks.findIndex('NOSE_BOTTOM_CENTER')].position);
+        nose.push(landmarks[landmarks.findIndex('NOSE_BOTTOM_LEFT')].position);
+        nose.push(landmarks[landmarks.findIndex('MIDPOINT_BETWEEN_EYES')].position);
+        nose.push(landmarks[landmarks.findIndex('NOSE_BOTTOM_RIGHT')].position);
+        result.drawPolyline(nose, false, result_color);
+
+        var mouth = [];
+        mouth.push(landmarks[landmarks.findIndex('MOUTH_LEFT')].position);
+        mouth.push(landmarks[landmarks.findIndex('UPPER_LIP')].position);
+        mouth.push(landmarks[landmarks.findIndex('MOUTH_RIGHT')].position);
+        mouth.push(landmarks[landmarks.findIndex('LOWER_LIP')].position);
+        mouth.push(landmarks[landmarks.findIndex('MOUTH_LEFT')].position);
+        mouth.push(landmarks[landmarks.findIndex('MOUTH_CENTER')].position);
+        mouth.push(landmarks[landmarks.findIndex('MOUTH_RIGHT')].position);
+        result.drawPolyline(mouth, false, result_color);
+
+    });
+
+    lcd.start(2, result.frameBuffer, result_format, function(){
+        console.log('lcd start callback');
+        setTimeout(function(){
+            lcd.startSync(1, background.frameBuffer, background_format);
+            lcd.stopSync(0);
+        }, timeout);
+    });
+}
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■ ランダムグラフィックス描画テスト
+var display = require('display');
+var Graphics = require('graphics').Graphics;
+var AlignedBuffer = require('aligned_buffer').AlignedBuffer;
+
+var width = 480;
+var height = 272;
+var graphics_format = 'rgb565';
+var text_format = 'argb4444';
+var pixel_bytes = 2;
+var alignment = 32;
+var color_mask = 0xFFFF;
+var text_color = 0xF000;
+var text_background = 0xFFFF;
+
+var buf_size = width * height * pixel_bytes;
+var interval = 10;
+var count = 500;
+
+var lcd_config = {
+    type : '4.3inch'
+};
+
+var graphcis_buf = new AlignedBuffer(buf_size, alignment);
+var canvas = new Graphics({buf:graphcis_buf, width:width, height:height, format:graphics_format});
+canvas.drawRect(0,0,width,height,0xFFFF,true);
+var text_buf = new AlignedBuffer(buf_size, alignment);
+var text = new Graphics({buf:text_buf, width:width, height:height, format:text_format});
+text.drawRect(0,0,width,height,0x0000,true);
+
+var start_index = 0;
+var drawRandomGraphics = [
+    { type : "Line",      func : drawRandomLine },
+    { type : "Rectangle", func : drawRandomRect },
+    { type : "Arc",       func : drawRandomArc },
+    { type : "Circle",    func : drawRandomCircle },
+    { type : "Ellipse",   func : drawRandomEllipse },
+    { type : "Polygon",   func : drawRandomPolygon }
+];
+
+function drawRandomLine(g) {
+    var sx = Math.floor(Math.random() * width);
+    var sy = Math.floor(Math.random() * height);
+    var ex = Math.floor(Math.random() * width);
+    var ey = Math.floor(Math.random() * height);
+    var color = Math.floor(Math.random() * color_mask);
+    g.drawLine(sx, sy, ex, ey, color);
+}
+
+function drawRandomRect(g) {
+    var x = Math.floor(Math.random() * width);
+    var y = Math.floor(Math.random() * height);
+    var w = Math.floor(Math.random() * width);
+    var h = Math.floor(Math.random() * height);
+    var color = Math.floor(Math.random() * color_mask);
+    var fill = Math.round(Math.random());
+    g.drawRect(x, y, w, h, color, fill);
+}
+
+function drawRandomArc(g) {
+    var x = Math.floor(Math.random() * width);
+    var y = Math.floor(Math.random() * height);
+    var radius = Math.floor(Math.random() * height);
+    var start = Math.floor(Math.random() * 360);
+    var end = Math.floor(Math.random() * 360);
+    var color = Math.floor(Math.random() * color_mask);
+    g.drawArc(x, y, radius, start, end, color);
+}
+
+function drawRandomCircle(g) {
+    var x = Math.floor(Math.random() * width);
+    var y = Math.floor(Math.random() * height);
+    var radius = Math.floor(Math.random() * height);
+    var color = Math.floor(Math.random() * color_mask);
+    var fill = Math.round(Math.random());
+    g.drawCircle(x, y, radius, color, fill);
+}
+
+function drawRandomEllipse(g) {
+    var cx = Math.floor(Math.random() * width);
+    var cy = Math.floor(Math.random() * height);
+    var rx = Math.floor(Math.random() * height);
+    var ry = Math.floor(Math.random() * height);
+    var color = Math.floor(Math.random() * color_mask);
+    var fill = Math.round(Math.random());
+    g.drawEllipse(cx, cy, rx, ry, color, fill);
+}
+
+function drawRandomPolygon(g) {
+    var x = Math.floor(Math.random() * width);
+    var y = Math.floor(Math.random() * height);
+    var radius = Math.floor(Math.random() * height);
+    var sides = Math.floor(Math.random() * 10) + 3;
+    var color = Math.floor(Math.random() * color_mask);
+    var fill = Math.round(Math.random());
+    g.drawPolygon(x, y, radius, sides, color, fill);
+}
+
+display.openLCD(lcd_config, function(err, lcd) {
+    if(!err) {
+        lcd.startSync(0, canvas.frameBuffer, graphics_format);
+        lcd.startSync(1, text.frameBuffer, text_format);
+
+        var draw = function(index) {
+            var type = drawRandomGraphics[index].type;
+            canvas.drawRect(0, 0, width, height, 0xFFFF, true);
+            text.drawRect(0, 0, width, height, 0x0000, true);
+            text.drawText('Draw Random ' + type, 0, 0, 2, text_color, text_background);
+
+            var timer = setInterval(function() {
+                drawRandomGraphics[index].func(canvas);
+            }, interval);
+
+            setTimeout(function() {
+                clearInterval(timer);
+                if(++index < drawRandomGraphics.length) {
+                    draw(index);
+                }
+            }, interval * count);
+        }
+        draw(start_index);
+    }
+});
+//eoc ---------- (EndOfCode)
+
+
+
+
+
+//boc ---------- (BeginOfCode)
+// ■ JPEG画像の連続表示テスト
+// 事前に contents_microSD.zip を解凍し、frames フォルダを microSD カード直下に
+// コピーしてください
+var display = require('display');
+var jpeg = require('jpeg');
+var fs = require('fs');
+var AlignedBuffer = require('aligned_buffer').AlignedBuffer;
+var Graphics = require('graphics').Graphics;
+
+var width = 480;
+var height = 272;
+var format = 'rgb565';
+var pixel_bytes = 2;
+var alignment = 32;
+var buf = new AlignedBuffer(width * height * pixel_bytes, alignment);
+var canvas = new Graphics({buf:buf, width:width, height:height, format:format});
+canvas.drawRect(0,0,width,height,0xFFFF,true);
+
+var lcd_config = {
+    type : '4.3inch'
+};
+
+var file_num = 14315;
+
+display.openLCD(lcd_config, function(err, lcd) {
+    if(!err) {
+        lcd.start(0, canvas.frameBuffer, format, function() {
+            var i = 1;
+            var interval = setInterval(function() {
+                if(i > file_num) {
+                    clearInterval(interval);
+                    return;
+                }
+                body(i++);
+            }, 1);
+        });
+    } else {
+        console.log(err);
+    }
+});
+
+function body(i) {
+    var dir = ('000' + Math.floor(i / 100)).slice(-3);
+    var file = ('00000' + i).slice(-5) + '.jpg';
+    var path = '/sd/frames/' + dir + '/' + file;
+    var jpeg_data = new AlignedBuffer(fs.readFileSync(path), alignment);
+
+    var decode_config = {
+        width : 384,
+        height : 216,
+        format : format
+    };
+
+    var image = jpeg.decodeSync(jpeg_data, decode_config);
+    canvas.drawImage(image, 48, 28);
+}
+//eoc ---------- (EndOfCode)