20190711のNode.jsに関する記事は7件です。

Error: Command failed: wmic os get Captionの対処方法

※Windowsでの対応です。

yoコマンドを使用したら以下のエラーが発生

Error: Command failed: wmic os get Caption

解決方法

C:\Windows\System32\wbemに対しパスを通せば良いようです。

参考
https://github.com/apache/cordova-cli/issues/434

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebアプリにVisual Regressionテストを導入

こんにちは、CyberAgentで内定者アルバイトをしています @junkisai です。
今回は「こえのブログ」の開発に導入したVisual Regressionテストについてお話させていただきます。

そもそもVisual Regressionテストとは

コンポーネントやページのスクリーンショットを以前のバージョンのものと比べて、ピクセルレベルでの差分を検出するテスト手法のことです。導入の目的としては、開発者が意図しない変更がないか/差分が意図したものかをテスト結果から確認するだけにすることで、人によるチェックの負担を軽減するためというのが最も大きいですが、機能や動作を検証するためのテストとしても有効です。

image.png

完成品

今回導入したVisual Regressionテストの流れ図は以下のようになります。

image.png

GitHubにpushすると、CircleCI上でpuppeteerを起動し、Webアプリの各ページのスクリーンショットを撮影します。その後、jest-image-snapshotを使って、撮影された画像とmaster branchの画像との間に差分がないか検証します。差分が検知された場合は、エラーを報告し、CircleCiのArtifacts内に検証結果画像が保存されます。なお、比較元のmaster branchの画像は、リリースされるタイミングでアップデートしています。

使用したツール

キツかったところ

1. 下準備が大変

前述した通り、Visual Regressionテストはスクリーンショットを撮影して検証します。そのため、APIから取得するリソースが変わるとデザイン的に問題ない(本来落ちるべきではない)ケースでテストが通らなくなります。

この問題は、APIのスタブサーバーを作成し、テスト実行時のみスタブサーバーにリクエストするように制御することで同じリソースで検証することができます。しかし、複数プロセスをいい感じに実行する必要があり(APIのスタブサーバー起動→アプリケーションサーバー起動→テスト実行)、scriptが複雑になってしまいました。

package.json
{
  "scripts": {
    // スタブサーバーの起動とVisualテストを同時実行
    "serve-snapshot": "run-p start:test-server -r snapshot",

    // APIのスタブサーバー起動(PORT=8082)
    "start:test-server": "npm run serve:stub", 

    // http://localhost:8082/stubApi/entriesが使えるようになるまで、テストの実行を待機
    "snapshot": "wait-on http://localhost:8082/stubApi/entries && jest -c test/visual/jest.config.js"
  }
}
test/visual/jest.config.js
module.exports = {
  preset: 'jest-puppeteer',
  testRegex: './*\\.test\\.js$',
};
jest-puppeteer.config.js
module.exports = {
  server: {
    command: `npm start`, // アプリケーションサーバー起動
    port: 8081,
    debug: true,
  }
};

※上記のコードは2番で紹介するpuppeteerのsetRequestInterceptionを使用すると、APIのスタブも複雑なscriptも書くことなく、同様の要件を実現できるかと思います。

2. スクショの結果がネットワーク環境に依存する

ネットワーク環境が悪かったり、CORBの問題からかCircleCI環境から画像の取得ができず、一貫したスクリーンショットが撮れないという問題が起こりました。そこでpuppeteerのsetRequestInterceptionを使用し、スクショの結果がネットワーク環境に依存しない工夫を施しました。

puppeteerの数ある機能の中に、リクエストをインターセプトしてハンドリングしてしまう機能が提供されています。この機能に関してはGoogle I/O'19にて紹介されていたので、詳しく知りたい方は以下の動画をご覧ください。

Modern Web Testing and Automation with Puppeteer (Google I/O’19) | youtube.com

この機能を利用して、assetsにリクエストを飛ばそうとしている場合はインターセプトしてローカル内の画像を返してあげることで、安定して同一のスクリーンショットを撮影することができるようになりました。

const getBufferImg = () => {
  const IMG_URL = `${__dirname}/mamehiko.jpg`;
  return fs.readFileSync(IMG_URL);
};

await page.setRequestInterception(true);
page.on('request', request => {
  if (
    request.resourceType() === 'image' &&
    request.url().indexOf('http://localhost:8082/assets') !== -1
  ) {
    request.respond({
      contentType: 'image/jpeg',
      body: mockImg,
    });
  } else {
    request.continue();
  }
});

image.png

mamehiko.jpg

(画像はこちらのブログから使用許可をいただきました。ありがとう、マメヒコくん。)

3. 画像の遅延読み込み対策

こえのブログ」は画像遅延読み込みに対応しています。puppeteerの仕様上でフルページのスクリーンショットを撮ろうとすると、ロード仕切れていない画像が表示されて、正しく撮影できない問題がありました。

この問題はissuesで報告されており、1度ページの底部までスクロールさせることで解決できるようなので下記のコードで対応しました。

const scrollToBottom = async page => {
  await page.evaluate(() => {
    let lastScrollTop = document.scrollingElement.scrollTop;
    const scroll = () => {
      document.scrollingElement.scrollTop += 400;
      if (document.scrollingElement.scrollTop !== lastScrollTop) {
        lastScrollTop = document.scrollingElement.scrollTop;
        requestAnimationFrame(scroll);
      }
    };
    scroll();
  });
};

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  /** 中略 **/
  await scrollToBottom(page);
  await page.waitFor(2000); // ページ底部の遅延読み込みを待機する
  await page.screenshot({ fullPage: true });
});

4. CircleCIに組み込む際に立ちはだかった壁

CircleCI上でpuppeteerを起動すると、日本語に対応していないためスクリーンショットが豆腐で一杯になります。

image.png

test実行前に日本語のフォントをインストールすることで、豆腐問題は解決することができました。しかしこの対応により、ローカルの環境で使用しているフォントとCircleCI上で使用しているフォントが違うことから、ローカルとCircleCIとでテストに使用する比較元の画像を切り分ける必要が出てきました。

そこで、CircleCIに比較元画像をキャッシュし、CI上でテストする際はキャッシュされた画像を参照することでローカルとCI環境の切り分けを行いました。

.circleci/config.yml
version: 2.0
# 中略
jobs:
  visual-test:
    <<: *defaults
    steps:
      - attach_workspace:
          at: .
      - run:
          name: Generate _version with version in lerna.json
          command: node -pe 'require("./lerna.json").version' > _version
      - restore_cache:
          name: Restore __image_snapshots__ cache
          key: image_snapshots-{{ checksum "_version" }}
          paths:
            - test/visual/__image_snapshots__/
      - run:
          name: Install japanese font
          command: sudo apt-get update && sudo apt-get install fonts-takao-gothic
      - run:
          name: Visual Regression Testing
          command: npm run serve-snapshot
      - store_artifacts:
          path: test/visual/__image_snapshots__/

終わりに

ページごとのVisual Regressionテストに関する記事が少なく、ゴールに到達するまで苦労しましたが、無事「こえのブログ」にこのテストを導入し、開発者の心理的安全の一助になれたかなと思います。またこの方法はフレームワークに依存しない上、puppeteerがなんでもできるおかげで下準備の敷居が下がっているため、導入しやすい方法なんじゃないかなと思っています(技術スタックの移行時とか効果を発揮しそう)。欲を言えば、差分検出結果をGithubやslack上に表示してあげるところまでやりたいところでした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsとJXAでMacのアプリが起動中かどうか調べる

AppleScriptでもいいですが、JXAだとワンライナーで完結していい感じです。

isAppRunning.js
const promisify = require("util").promisify;
const exec = require("child_process").exec;
const execp = promisify(exec);

const appName = "Slack";

(async () => {
  const { stdout } = await execp(
    `osascript -l JavaScript -e "Application('${appName}').running()"`
  );
  console.log({ stdout });
})();

注意点としては、返り値がBooleanっぽいけどStringだと言うことです。
上の書き方だと {stdout: 'true\n'}のような感じになります。
これ対策がよくわからなかった。

他にもいい方法があったらぜひ。調べてもあんまり見つからないんですよね。
たぶんSwiftが使えればもっとつおくなれる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WindowsでNode.jsから仮想環境作って動かす

※コチラの記事はブログからのほぼ転載です。

インストール


ざっくり動かすまでの方法書くと、『本家』から、Windowsのbitの合ってるものをDLして、インストールで完了。
余談だが『nullivex/nodist』を使うとバージョン管理に便利らしい。

解りやすい説明サイトがあったから、ここ参照したらいいと思います。
Windows上でNode.jsを実行してHello worldをブラウザに表示する方法 - Qiita

Node.js使ってみる

コマンドプロンプトとかコマンド実行できるもの(以下:コマンド)を使っていく。

node -v

これが上手く動かなかった場合、パスが通っていない可能性がある。

通していない場合、使うとき(※windowsの場合の例)

cd C:\Program Files\nodejs\node.exe D:\aaa\ex.js

としたくなるが、起動しなくて、node.jsのディレクトリまで1回移動してから命令しなくてはいけない。

つまりこうなる。

cd C:\Program Files\nodejs\
node.exe D:\aaa\ex.js

めんどくさいので、パス通すことをオススメする。

サーバーを構築して動かす

サーバを試しに動かすのに、最適だった、本家でも使っていた『node_chat』は、もはや過去の産物だから使えぬ。
諦めて別の方法でサーバを動かしていく。

WEBサーバー起動で、単純な表示させてみる

『hello.js』作る。仮にポートは900とした。

const http = require('http');
const PORT = 900;
const server = http.createServer((req, res)=>{
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World!!');
});
server.listen(PORT);
console.log(`Server running at http://localhost:${PORT}/`);

作ったらコマンドから

node hello.js

するとブラウザから、下記のどれでもアクセス出来る。

localhostは近年ブラウザに嫌われてるから、個人的にオススメしたくない。

他の仮想環境起動してようが、Apache起動してようが、関係ない。
ただ他のとこから、Node.jsで使用しているポート(今回の場合は900)だけは、使わないように注意する。

一応書くと、PORTの設定を80にするなら、URLにポートの指定は不要。
動かし方はこれでOK。

WEBサーバー起動して、外部ファイルを読み込ませてみよう

読み込ませるファイルは下記の状態。

  • 読み込むファイルは『/test/index.html』
  • index.htmlと同じ階層に『js.js』『css.css』『hk.png』を置き、index.html内で使用
  • ポートは900

ファイルの位置関係はこんな感じ。

./処理.js
./test/index.html
./test/js.js
./test/css.css
./test/hk.png

外部ファイル読み込み1:単純ファイル名指定

『load.js』とした。

const http = require("http");
const PORT = 900;
const fs = require("fs");

const server = http.createServer((req, res) => {
  fs.readFile("./test/index.html", function(err, data) {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end(data, "utf-8");
  });
});
server.listen(PORT);
console.log(`Server running at http://localhost:${PORT}/`);

そしてコマンドで呼び出す ※以下同様

node load.js

読み込み対象ファイルがない場合、真っ白画面が表示される。
createServerでwriteHeadされているので、何も内容のないhtmlページがあるのだから、Not Findにはならない。

外部ファイル読み込み2:参照ファイルも読み込ませる

読み込み対象のCSSとかJSが読まれていない。階層違うからね。
不満なので、読み込むように処理を変更させる。
『app.js』とした。

Node.jsで書いたWebサーバでJSやCSSを読み込む - Qiita』を基本にしてるが、1個1個全て設定はメンドウ。
file-typeモジュール使えるとこは、使うことにした。

npm install --save file-type

file-typeモジュール対象外であれば『undefined』を返してくる。

const http = require("http");
const fs = require("fs");
const PORT = 900;
const filetype = require("file-type");
// ファイル保存場所
const dirpath = "./test";
//const dirpath = __dirname + "/test";

const server = http.createServer(function(req, res) {
  var url = req.url; // リクエストからURL取得
  if (url == "/") {
    // ディレクトリのみ指定の場合は『index.html』固定
    url = "/index.html";
  }
  var path = dirpath + url; //リクエストURLを相対パスへ変換
  console.log(path);

  var tmp = path.split("."); //『.』区切りで配列にする
  var ext = tmp[tmp.length - 1]; // tmp配列の最後の要素(拡張子)を取得
  try {
    var buf = fs.readFileSync(path); // encoding指定しなければBuffer取得
  } catch (error) {
    console.log('not find!');
    return false;
  }
  var type = filetype(buf);
  console.log(type);

  // 拡張子判定
  switch (ext) {
    case "js": // .js = JavaScript
      fs.readFile(path, function(err, data) {
        res.writeHead(200, { "Content-Type": "text/javascript" });
        res.end(data, "utf-8");
      });
      break;
    case "css": // .css = StyleSheet
      fs.readFile(path, function(err, data) {
        res.writeHead(200, { "Content-Type": "text/css" });
        res.end(data, "utf-8");
      });
      break;
    case "html": // .html = html
      fs.readFile(path, function(err, data) {
        res.writeHead(200, { "Content-Type": "text/html" });
        res.end(data, "utf-8");
      });
      break;
    default:
      // 他はfiletypeの自動判定
      fs.readFile(path, function(err, data) {
        res.writeHead(200, { "Content-Type": type.mime });
        res.end(data);
      });
      break;
  }
});
server.listen(PORT);
console.log(`Server running at http://localhost:${PORT}/`);

読み込み対象ファイルがない場合、その旨メッセージ出すようにした。
じゃないとnode.jsが強制終了しちゃうから…

readFileSyncはエンコードを指定すれば、Bufferでなくファイル内容を読める。

var file = fs.readFileSync(path,{encoding: "utf-8"});

ファイルが無ければ止まるから注意。

外部ファイル読み込み3:Expressモジュールを使う

先に書いた本家が使ってたchatで、Expressモジュール使ってみる。

Expressモジュール使おうと思ったら、標準で入ってなかったので、入れる必要がある。

npm install --save express

因みにアンインストール時は

npm uninstall express --save

今回は『exp.js』とする。

const express = require("express");
const app = express();
const PORT = 900;

const server = app.listen(PORT, function(){
    var host = server.address().address;
    var port = server.address().port;
    console.log('Server running at http://%s:%s', host, port);
});

// 同ディレクトリ内と判断する
app.use(express.static("test")); // 相対
// app.use(express.static(__dirname + '/test'));   // 絶対

app.get()と戦っていたが、ファイルがあって、処理をさせるでもなければ、不要だった。
シンプルだし、早い。

ファイルがない場合は、

Cannot GET /index.html

と見つからなかったファイル情報が表示される。

まとめ

モジュールって便利っすね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nexe Tips

Node.jsアプリを実行ファイルにビルドするツールnexeのTips

複数のパターンのアセットをバンドルする

nexeではドキュメントに記載のとおり、以下のようにresourceに指定したファイルをバンドルできる。

nexe server.js --resource "public/**/*.html"

nextは内部でglobbyを使用しているのでglobbyで対応しているフォーマットは使える。

以下のようにすると複数パターンを指定できる

nexe server.js --resource "{config/*.json, *.jpg}"

プラットフォーム指定

targetを指定する

ドキュメントに従う

nexe server.js --target linux-x64
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ts-node で TypeScript + node をサクッと実行する

node でさっくりと TypeScript を実行したいときに、babel なり webpack なりで transpile して実行するのは不便です。しかも型が考慮されなくなってしまいますね。

そういうときは ts-node を使うことで、バベってファイルを生成してから食わせずとも、そのまま実行できるので手軽です。型も考慮されます。

使い方

さっくりと依存を追加します:

npm install --save typescript ts-node

tsconfig.json もついでに生成しておきます:

> ./node_modules/.bin/tsc --init

message TS6071: Successfully created a tsconfig.json file.

試しに適当なコードを書いてみます:

src/main.ts
const main = () => {
    console.log('It works!');
};

main();

動かしてみます:

> ./node_modules/.bin/ts-node src/main.ts

It works!

いい感じにすぐ実行されました。

もちろん型が誤っている場合もきちんとエラーになりますし、元のファイルの情報が表示されます:

src/main.ts
const main = (str: string) => {
    console.log('It works!');
};

main(1111);
TSError: ⨯ Unable to compile TypeScript:
src/main.ts:5:6 - error TS2345: Argument of type '1111' is not assignable to parameter of type 'string'.

5 main(1111);
       ~~~~

:thumbsup:

エイリアス

import some from '../../a/b.ts' とかになってややこしいので、パスのエイリアスを登録しておくことが結構あると思います:

tsconfig.json
{
  "compilerOptions": {
    // ...
    "baseUrl": "./",
    "paths": {
      "#/*": ["src/*"]
    },
    // ...
  }
}

この例では

src/x.ts
import some from '#/a/b.ts';
// <=> import some from './a/b.ts';
src/i/j/k.ts
import some from '#/a/b.ts';
// <=> import some from '../../a/b.ts';

となります。捗りますね。

これで import するスクリプトの階層にかかわらず、ソースルートディレクトリからのパスとして記述できるので、すっきりとしてわかりやすくなります。VSCode でもエイリアスが効いていい感じです。

Vue でも @/components/a.ts で参照できるようなエイリアスになってますが、似たような感じですね。 (あれは webpack のエイリアスのようですが)。


例として次のようなファイル構成にしてみましょう:

src/mod-a/build.ts
export default (): string => 'mod-a';
src/mod-b/repeat.ts
import build from '#/mod-a/build';

export default (number: number): string => {
    let queue = [];
    const value = build();
    for (let i = 0; i < number; ++i) {
        queue.push(value);
    }

    return queue.join(' | ');
}
src/main.ts
import repeat from "#/mod-b/repeat";

const main = () => {
    console.log(repeat(5));
};

main();

さて、これでエイリアスを使って読み込めたので
実行してみたいと思います:

> ./node_modules/.bin/ts-node src/main.ts

Error: Cannot find module '#/mod-b/repeat'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:581:15)
    at Function.Module._load (internal/modules/cjs/loader.js:507:25)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
    at Object.<anonymous> (/Users/user/Sandbox/my-ts-project/src/main.ts:1:1)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Module.m._compile (/Users/user/Sandbox/my-ts-project/node_modules/ts-node/src/index.ts:473:23)
    at Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Object.require.extensions.(anonymous function) [as .ts] (/Users/user/Sandbox/my-ts-project/node_modules/ts-node/src/index.ts:476:12)
    at Module.load (internal/modules/cjs/loader.js:599:32)

しかしながら、これは import が解決できずエラーになります。なぜでしょうか。

この問題は
tsconfig-paths を追加して、require してあげることで解決できます:

npm install --save tsconfig-paths
> ./node_modules/.bin/ts-node --files -r tsconfig-paths/register src/main.ts

mod-a | mod-a | mod-a | mod-a | mod-a

tsconfig-pathsrequire してあげることで、tsconfig.json で定義したエイリアスで import を解決できるようになりました。

ただ、毎回入れるのは面倒なので package.jsonscripts に追加して npm start 一発で実行できるようにすると楽でいいです:

package.json
{
  // ...
  "scripts": {
    "start": "ts-node --files -r tsconfig-paths/register",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}
npm start src/main.ts

:thumbsup:

型定義

自分で型定義 (*.d.ts) を持っている場合は、これも tsconfig.json に追加します:

tsconfig.json
{
  "compilerOptions": {
    // ...
    "typeRoots": ["./src/types"],
    // ...
}

テスト

テストに関しては別エントリで記述しました。同じくバベらずとも ts-jest を使うことで型を活かしながら簡単にテストができます:

? TypeScript のテストを Jest (ts-jest) でやってみる


これで TypeScript で手元の適当なコードを動かしたいときから、バッチ等でゴリゴリ動かしたいときまで簡単に対応できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Groovy-IoTをNode.jsから操ってみた

今回遊ぶのは、大宮技研さんの「Groovy-IoT」です。
 http://www.omiya-giken.com/?page_id=2262

世の中にたくさん出回っているGroveセンサをつなぐことができます。
ホストPCとはUSBで接続するので、Raspberry-Piなどの一般的なPCやマイコンで制御することができるようになります。
今回は、ホストPCとして、Raspberry-Piを使います。

大宮技研さんの方で、「Kikori」というclojureベースのツールを用意していただいているのですが、もっと気軽に扱いたかったので、Node.jsから扱えるようにネイティブ拡張モジュールを作ってみました。

(参考) Kikori
 https://gitlab.com/myst3m/kikori

それから、Groveセンサを操作するためのライブラリは、Arduinoが多いので、ネイティブ拡張モジュールのAPIは、ArduinoのGPIOとI2CとSerialのそれに似た形してみました。

今回サンプルとして動かしてみるのは、「Grove - LED Bar」です。
 https://www.seeedstudio.com/Grove-LED-Bar-v2-0.html

ソースコードもろもろをGithubに上げておきました。
 https://github.com/poruruba/node-groovy-iot

Groovy-IoT をC言語で触ってみる

Groovy-IoT には、Microchip製のMCP2221Aというチップが乗っています。
そこにI2C、GPIO、UARTのペリフェラルがありGroveモジュールの接続端子に接続されています。
USBでGroovy-IoTとホストPCを接続するのですが、操作はHIDで行うので、ホストPC側はHIDさえ扱えればよいです。
ちょうど、MPC2221用にライブラリを提供していただいている方がいましたので、ありがたく使わせていただきました。

libmcp2221
 https://github.com/zkemble/libmcp2221

コンパイルに外部ライブラリが必要ですので、インストールしておきます。

> apt-get install libudev-dev libusb-1.0-0-dev

以下のファイルを使いますので、libmcp2221のWebページを参考にダウンロードしておきます。

  • libmcp2221.c
  • libmcp2221.h
  • hid.c
  • hidapi.h

もう一つ補足しておきます。
ホストPCに接続すると、HIDのほかに、仮想COMポートも割り当たります。
MCP2221AのペリフェラルとしてUARTがあり、Grove端子に割り当てられていますが、それにアクセスするには、ホストPC側に現れた仮想COMポートを使うのです。

dmesgを載せておきます。

[164546.769340] Indeed it is in host mode hprt0 = 00021501
[164546.979214] usb 1-1: new full-speed USB device number 13 using dwc_otg
[164546.979575] Indeed it is in host mode hprt0 = 00021501
[164547.222987] usb 1-1: New USB device found, idVendor=04d8, idProduct=00dd, bcdDevice= 1.00
[164547.223008] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[164547.223017] usb 1-1: Product: MCP2221 USB-I2C/UART Combo
[164547.223026] usb 1-1: Manufacturer: Microchip Technology Inc.
[164547.240438] cdc_acm 1-1:1.0: ttyACM0: USB ACM device
[164547.250507] hid-generic 0003:04D8:00DD.000C: hiddev96,hidraw0: USB HID v1.11 Device [Microchip Technology Inc. MCP2221 USB-I2C/UART Combo] on usb-20980000.usb-1/input2

Groovy-IoT を一般ユーザで触る

Groovy-IoTをRaspberry-Piにつなぐと、特に追加のデバイスドライバのインストールをせずとも、自動的に認識してくれるので楽ちんです。
ですが、そのままではルート権限を持ったユーザしかアクセスできませんので、一般ユーザでもアクセスできるようにします。

まず、HIDアクセスです。
/etc/udev/rules.d 配下に、以下のファイルを作成して配置します。

99-hid.rules
KERNEL=="hidraw*", ATTRS{busnum}=="1", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="00dd", MODE="0666"

次に、仮想COMポートです。
仮想COMポートは、/dev/ttyACM0 として見えますが、この所有者groupはdialoutです。ですので、そのgroupに、アクセスしたい一般ユーザを追加すればよいです。

> sudo gpasswd -a ユーザ名 dialout

念のため、設定後、Raspberry Piを再起動しましょう。

Arduino っぽく扱う

以下、こんな感じのC言語の関数にしてみました。
Arduinoっぽくないですか?ソースコードは割愛します。Githubを参照して下さい。

mcp2221.h
#ifndef _MCP2221_H_
#define _MCP2221_H_

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

long MCP2221_initialize(unsigned char sport);

long Wire_initialize(void);
long Wire_dispose(void);
void Wire_beginTransmission(unsigned char address);
int Wire_write_byte(unsigned char value);
int Wire_write_str(const char *p_str);
int Wire_write_array(const unsigned char *p_data, int length);
void Wire_endTransmission(void);
int Wire_requestFrom(unsigned char address, int count);
int Wire_available(void);
unsigned char Wire_read(void);

#define HIGH    1
#define LOW     0

#define INPUT           0
#define OUTPUT          1
#define INPUT_PULLUP    2

long GPIO_initialize(void);
void pinMode(unsigned char pin, unsigned char mode);
void digitalWrite(unsigned char pin, unsigned char value);
unsigned char digitalRead(unsigned char pin);

long Serial_begin(unsigned long speed);
void Serial_end(void);
int Serial_available(void);
int Serial_read(void);
int Serial_peek(void);
void Serial_flush(void);
int Serial_write_byte(unsigned char val);
int Serial_write_str(const char *p_str);
int Serial_write_array(const unsigned char *p_str, int len);
int Serial_print(const char *data);
int Serial_println(const char *data);

#ifdef __cplusplus
}
#endif // __cplusplus

#endif

Node.jsネイティブ拡張モジュールを作成する

ネイティブ拡張モジュールを作成するために、node-gypを使います。

> npm install -g node-gyp

まずは適当なフォルダを作成しましょう

> mkdir mcp2221native
> cd mcp2221native

binding.gypを作成します。

binding.gyp
{
    "targets": [
    {
        "target_name": "mcp2221native",
        "sources": [ "mcp2221native.cc", "mcp2221.c", "hid.c", "libmcp2221.c", "utility.cc"],
        "libraries": [
            "-ludev"
        ]
    }
    ]
}

上記gypファイル見てわかる通り、以下のファイルを使います。

  • mcp2221native.cc:これがネイティブ拡張モジュールの肝となるファイルです。
  • mcp2221.c:先ほど作成しました。
  • hid.c:ダウンロードしておいたファイルです。
  • libmcp2221.c:ダウンロードしておいたファイルです。
  • utility.cc:ユーティリティを集めたものです。作成しておきました。

mcp2221native.cc がネイティブ拡張の中心的な役割を果たしているのですが、やっていることは、Node.jsから呼ばれるI/Fとそれをmcp2221.cで作成した関数に橋渡ししているだけです。

ちなみに、"-ludev" は、内部でudevを使っており、このライブラリをリンクする必要があるためです。

まずは、以下。コンパイルの下準備だそうです。フォルダやMakefileを作ってくれます。

> node-gyp configure

以下で、ソースコードをコンパイルします。

> node-gyp build

これで、フォルダ「mcp2221native\build\Release」に、「mcp2221native.node」ができあがりました。

つぎに、このnodeファイルを、Nodeから、require('mcp2221native') って感じで使えるようにします。

どこか適当なフォルダを作成し、そこにコピー(もしくはシンボリックリンク)します。
例えば、以下の通りです。

> mkdir ~/node_native
> cp mcp2221native/build/Release/mcp2221native.node ~/node_native/

次に、Nodeがこれを検索できるようにします。
.bashrcの最後に以下を追記します。

export NODE_PATH=~/node_native

これでOKです。.bashrcを反映するために以下を実行しておきます。

source ~/.bashrc

Groove LED Barを使ってみる

さっそく、Groovy-IoTとそれにつないだGroveセンサを操作するNode.jsのソースを作成ましょう。
以下のような感じでGroovy-IoTにアクセスできます。
詳細は、Githubのmcp2221_test/index.jsを参考にしてください。

index.js
// まずはrequreして。。。
const mcp2221 = require('mcp2221native');
// Groovy-IoTに接続して。。。
var ret = mcp2221.initialize(0);
// ペリフェラルを初期化して。。。
ret = mcp2221.GPIO_initialize();
ret = mcp2221.Wire_initialize();
ret = mcp2221.Serial_begin(115200);
// 例えばこんな感じ。。。
mcp2221.GPIO_pinMode(__pinClock, OUTPUT);
mcp2221.GPIO_pinMode(__pinData, OUTPUT);

あとがき

MCP2221は結構遅い。さらにNodeネイティブ拡張ライブラリを挟んでいるのでさらに遅い。。。

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む