2017年10月24日火曜日

ベータ版やけど、GCP Cloud Functionsを試してみました

あまプロではシステム開発の案件を受ける事が有ります。
先日、GCP Cloud Functionsを使う機会が有りましたので、あメログ。

GCPとはGoogle社が提供する色んなサービスの内、主にシステム開発に特化したサービスを集めたサービス群です。ユーザ登録と支払い方法登録(クレジットカードが要ります)さえすれば、無料で始められます。
AWSとかAzureとかと似た様な物やと思って下さい。

今回の案件は「端末から定期的に送られてくる位置情報をサイトに表示する」という物です。
出来るだけ費用を掛けずに作りたいので、
今回使ったのは、クラウドファンクションズとデータストアとクラウドストレージという3つのサービスです。
各サービスを簡単に説明すると
  • CloudFunctions
    簡単なサーバ側の処理(関数)を処理単位で利用出来るサービス
    サーバを意
    今の所、使用出来る言語はNode.jsのみ(今後増える予定らしい)
    関数毎にURL(エンドポイント)が与えられる
  • DataStore
    簡単なSQL(GQL)も使えるデータベースのサービス
  • CloudStorage
    ファイル置き場
    HTML、JavaScript、CSS、画像、動画が置ける…つまり静的なサイトを構築出来るサービス
こんな感じです。
GoogleCloudPlatformには他にも似た様なサービスが色々有るんですが、無料枠もしくは格安なサービスを選んでみました。

それと上記のサービスはGCP上で完結します。つまり、ブラウザさえ有れば、開発マシンに何もインストールせずに開発出来てしまいます…凄い時代になったもんだ。
勿論、本格的に開発したい場合は開発マシンにツールのインストールが必須になります。
あくまで、 簡単に開発出来ますよ、という意味です。

先ず、端末からGPS情報を受け取って、データストアへ保存する部分
CloudFunctionコンソールを開いて関数の作成を押下します。
初めて関数を利用する場合は、最初にAPIを有効にするボタンを押下する必要が有ります。
次に名前、リージョン、割り当てられるメモリ、タイムアウト、トリガー(HTTPトリガー)、ソースコード(インラインエディタでindex.jsとpackage.json)、ステージバケット(無ければ参照ボタンを押下してバケットを作成)、実行する関数
項目は沢山有りますが、殆ど初期値で良いです。

編集するのはソースコードの部分。ここにnode.jsを記述します。
最初は簡単なHelloWorldが記述されてるので、これをインラインエディタで編集していきます。
このインラインエディタは正直使い難いので、ローカルで手早く開発したい方はこちらを参考にgcloudというツールをインストールして下さい。ローカルで開発して、zip圧縮してアップロードという流れになります。

データはGETで渡すので、GET値を取得する部分と、データストアへ保存する部分を記述します。
今の所まとまった日本語の資料が無いんですが、入門ガイドAPIリファレンスから情報を収集してプログラミングしました。
  • データストアではエンティティという単位で情報を扱う事
    RDBのレコードにみたいな物
  • エンティティにはキーが必要な事
  • 特殊な型(今回の場合は位置情報)を保存する事
    データストアには位置情報型が用意されているので、この型を用います
 この辺りに気を付けてプログラミングします。
プロジェクトIDやエンティティの種類は適宜記述して下さい。
index.jsは
 var url = require( 'url');
/**
 * Responds to any HTTP request that can provide a "message" field in the body.
 *
 * @param {!Object} req Cloud Function request context.
 * @param {!Object} res Cloud Function response context.
 */
exports.helloWorld = function helloWorld( req, res) {
  if( req.method == 'GET') {
    var url_parts = url.parse( req.url, true);
    console.log( url_parts.query.weight);
    var datastore = require('@google-cloud/datastore')({
      projectId: 'プロジェクトのIDを記述',
//      keyFilename: '/path/to/keyfile.json' インラインエディタの場合、キーファイルは不要
    });
    const entity = {
      key: datastore.key( [ 'エンティティの種類を記述', url_parts.query.ID]),
      data: {
        latLng:
          datastore.geoPoint( {
            latitude: parseFloat( url_parts.query.lat),
            longitude: parseFloat( url_parts.query.lon)
          }),
        weight: datastore.double( parseFloat( url_parts.query.weight))
      }
    };

    datastore.upsert( entity)
      .then( () => {
      // Task inserted successfully.
      res.status(200).send('Success: entryed');
    });
  } else {
    res.status(400).send('No message defined!');
  }
};
node.jsではパッケージを管理するにはこのファイルを使います
ここに、データストアの行を追加します package.jsonは
{
  "name": "sample-http",
  "version": "0.0.1",
  "dependencies": {
    "@google-cloud/datastore": "latest"
  }
}
こんな感じです。
作成ボタンを押下すると関数がデプロイされます(コンパイルエラーが出た場合は適宜修正して)。
次にブラウザからHTTPトリガーのURLにアクセスします
https://us-central1-develop-173700.cloudfunctions.net/mottainai-push?ID=5629499534213120&lat=34.001&lon=135.5&weight=2.3
ブラウザでなくてもcurlコマンドでもプログラム内のHttpClientでも何でも良いです。

間違ってなければSuccseと表示されます
データストアのコンソールを表示すると、エンティティが一つ追加されてる筈です。

では次にデータストアから座標を取得するHTTPトリガーを作成します
こちらは単純に求められたIDの座標を返すだけです
  • 座標は他ドメインで使用する想定なので、レスポンスヘッダでCORSを許可します
index.jsは
var url = require( 'url');

/**
 * Responds to any HTTP request that can provide a "message" field in the body.
 *
 * @param {!Object} req Cloud Function request context.
 * @param {!Object} res Cloud Function response context.
 */
exports.helloWorld = function helloWorld( req, res) {
  // Example input: {"message": "Hello!"}
  if( req.method=='GET') {
    // Everything is okay.
    var datastore = require('@google-cloud/datastore')({
      projectId: 'develop-173700',
//      keyFilename: '/path/to/keyfile.json'
    });
   
    datastore.get( datastore.key( [ 'mottainai', url.parse( req.url, true).query.ID]))
      .then( (results) => {
      // ID found.
      const entity = results[ 0];
     
      console.log( url.parse( req.url, true).query.ID);
      console.log( JSON.stringify( entity));
     
      res.header( 'Access-Control-Allow-Origin', req.headers.origin);
      res.header( 'Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
      res.header( 'Access-Control-Allow-Methods', 'GET');
      res.header( 'Access-Control-Allow-Credentials', true);
      res.status( 200).send( entity.latLng.latitude +','+ entity.latLng.longitude +','+ entity.weight);
    });
   
  } else {
    res.status( 400).send( 'No message defined!');
  }
};
 こんな感じ。
package.jsonは全く同じなので省略します。
 先程と同じく、作成ボタンを押下して関数をデプロイします(完了までちょびっとお待ち下さい)。

デプロイ完了後にHTTPトリガーのURLへアクセスすると
https://us-central1-develop-173700.cloudfunctions.net/mottainai-pull?ID=5629499534213120
先程保存されたエンティティの位置情報が取得できる筈です。

 これで、データの保存と取得の仕組みがサーバを用意せずに実現できました。

 最後にクラウドストレージに見た目となるHTMLを配置します。
 バケットを作成して、その中にHTMLを配置して、公開リンクのチェックを付けるだけです。
 このバケットはクラウドファンクションズで設定したバケットと同じにしといた方が解り易いです(別のバケットでも可能)。
今回は取得した座標を真ん中にしたグーグルマップを表示するのを作ってみました。
グーグルマップの表示にはAPIKeyが必要です。そちはらスタートアップガイドをご覧下さい。

ちなみにクラウドファンクションズで設定したバケットには作成した関数の履歴がzip形式で保存されます。

 幾らクラウドストレージが格安とはいえ、最新版のファイルとjson以外は要らないので、定期的に削除しておきましょう。

以上の様な工程で、クラウドストレージを用いた簡単サービスの作成になります。
Webサーバを用いたセッションの管理(つまりログインの機能)は使えないんですが、セッションに依存しない簡単な機能であれば無料(もしくは格安)で実装出来てしまいます。
例えば、定期実行するログの収集や、単純な集計等はこれで実現できそうです〜。

2017年10月21日土曜日

RaspberryPiでUSBメモリの日本語ファイル名の文字化けについて

あまプロではRaspberryPiを使った組み込みプログラミングもお教えしています。

ラズパイにはUSBが付いてまして、そこにマウスやキーボードを指す事が出来ます。
当然USBメモリも指す事が出来るのですが、あるラズパイではUSBメモリ内の日本語ファイル名が文字化けするという現象が起きてました。

他のラズパイでは日本語ファイル名の文字化けは起きてないので、何か設定の違いで起きている現象だと考えられます。
色々調べた所、usbmountというパッケージが原因という事が判りました。
どうやらこのusbmountというパッケージをインストールすると、USBメモリ内の日本語ファイル名が文字化けてまう様です…。
そもそもusbmountパッケージ無しでもラズパイは自動的にUSBメモリが挿入されると認識してマウントしてくれるので、usbmountパッケージは削除しました。

これで日本語ファイル名が文字化けする問題が解決しました。