まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

IBM Cloud から提供されているユーザーディレクトリ管理サービスである App ID の UI カスタマイズに挑戦しています。自分的にはまだ道半ばではありますが、途中経過という意味でいったん公開・紹介します。


【IBM Cloud AppID とは?】
IBM Cloud App ID サービス(以下「App ID」)はアプリケーションで利用するユーザーディレクトリを管理するサービスです。オンラインサインアップを含めたユーザー管理機能を持ち、アプリケーションにログイン機能を付加させたい場合に非常に簡単にログイン機能を実装できるようになります。IBM Cloud の(無料の)ライトアカウントを持っていれば(2要素認証など、一部機能が使えなかったり、1日の実行回数などに制限もありますが)無料のライトプランで利用することも可能です:
2021052900



【IBM Cloud AppID のカスタマイズに挑戦した背景】
App ID はユーザー管理機能が必要なアプリケーション開発を行う上で非常に便利な一方、UIのカスタマイズに関する情報が少なく(管理メニューに用意されているのは、ログイン画面内にロゴ画像を貼り付けることができる程度)、ほぼあらかじめ用意された(英語メインの)画面を利用する必要がありました。

機能的には満足なのですが、この UI カスタマイズがどの程度厄介なものなのかを調べる意味も含めて、AppID の UI カスタマイズに挑戦してみました。結論として 2021/06/02 のこのブログエントリ公開時点では 100% の実装ができているわけではないのですが、ログイン画面およびパスワードリセット画面のカスタマイズには成功しているため、ここでいったん公開することにしました。


【IBM Cloud AppID のカスタマイズサンプル】
AppID UI カスタマイズを使ったサンプルアプリケーションのソースコードはこちらで公開しています:
https://github.com/dotnsf/appid_fullcustom


サンプルアプリケーションを利用するには IBM Cloud にログインして、App ID サービスのインスタンスを作成し、サービス資格情報を作成・参照する必要があります:
2021060201


そして以下の値を確認します:
{
  "apikey": "*****",
  "appidServiceEndpoint": "https://us-south.appid.cloud.ibm.com",
  "clientId": "*****",
  "secret": "*****",
  "tenantId": "*****",
    :
    :
}

これらの値をソースコード内 settings.js のそれぞれの変数に代入して保存します:
//. IBM App ID
exports.region = 'us-south';
exports.tenantId = '*****';
exports.apiKey = '*****';
exports.secret = '*****';
exports.clientId = '*****';

exports.redirectUri = 'http://localhost:8080/appid/callback';
exports.oauthServerUrl = 'https://' + exports.region + '.appid.cloud.ibm.com/oauth/v4/' + exports.tenantId;


※exports.region の値は appidServiceEndpoint の値の "https://" と ".appid.cloud.ibm.com" の間の文字列です。それ以外はサービス資格情報にかかれている値をそのまま記載します。

また exports.redirectUri の値はアプリケーションの OAuth ログインのリダイレクト先 URL を記載します。このサンプルアプリケーションでは 8080 番ポートで待ち受けて /appid/callback にリダイレクトする想定で作られているのでこのような値になりますが、実際にパブリックなインターネットで利用する場合はこの値を実際の URL 値に書き換えてください。

準備の最後に AppID サービスのリダイレクト URI にここで指定した export.redirectUri の値と同じものを登録します。サービスの「認証の管理」メニューから「認証設定」タブを選択し、「Web リダイレクト URL の登録」欄に export.redirectUri に指定した値と同じものを追加してください。これで準備は完了です:
2021060202


このサンプルアプリケーションを実際に動かしてみましょう。まずは npm install して node app で起動します:
$ cd appid_fullcustom

$ npm install

$ node app

8080 番ポートで待ち受ける形で起動するので、ウェブブラウザで http://localhost:8080/ にアクセスします。正しく動作すると http://localhost:8080/login にリダイレクトされ、以下のようなシンプルなログイン画面になります(この画面はカスタムUIで作った画面です):
2021060203


AppID サービスに登録した ID とパスワードを入力して "Login" します:
2021060204


ログインに成功すると、そのユーザーの名前などが表示される画面になります。この画面で "logout" すると元のログイン画面に戻ります:
2021060205


ログイン画面下部に2つのリンクがあります。「オンラインサインアップ」と書かれたリンクをクリックすると、オンラインサインアップ用の画面に切り替わります(この画面もカスタム UI です):
2021060201


ここでメールアドレスなどを入力してサインアップ可能です:
2021060202


サインアップしてしばらく待つと、メールアドレスの有効性確認を行う必要があるため、指定したメールアドレスにメールが届きます(なお、自分の環境では「迷惑メール」扱いで届きました(苦笑))。メールを開いてリンクをクリックし、有効性確認処理をしてください:
2021060203


メールアドレスの有効性が確認されるとオンラインサインアップが完了したとみなされ、これ以降はメールアドレスとサインアップ時に指定したパスワードでログインできるようになります:
2021060203


ログイン画面下部のもう1つのリンク「パスワードを忘れた場合」はパスワードリセット用のリンクです:
2021060204


こちらをクリックするとパスワードを忘れてしまったメールアドレスを指定する画面(これもカスタムUIです)が表示されるので、ここにパスワードを忘れたアカウントのメールアドレスを指定します:
2021060205


すると指定したメールアドレスにパスワードリセット用の URL が書かれたメールが届きます。このメールを開いて、URL にアクセスします:
2021060206


すると新しいパスワードを入力する画面(この画面だけはカスタムUIではなく、あらかじめ用意されたものです)が表示されるので、新しいパスワードを入力します:
2021060207


これで新しいパスワードを使って再びログインが可能になります:
2021060203


念の為、カスタマイズ無しの場合の AppID の各種画面UIを以下に紹介しておきます。(Your Logo Here) と書かれた箇所にロゴ画像を貼り付けることは可能ですが、それ以外のカスタマイズをしようとすると今回紹介したサンプルのような方法を取る必要があります。その代わり、これらの画面を使う場合は非常に簡単に実装することができるものです。

(ログイン画面)
2021060301

(パスワード忘れ)
2021060302

(オンラインサインアップ)
2021060303



【まとめ】
以上、AppID を独自のカスタム UI で利用する手順を紹介しました。一連の手順の中でパスワードリセット時の新パスワードを指定する画面だけは元の(英語の)UIになってしまっていますが、それ以外はすべて独自の UI で実現できているのがおわかりいただけると思います。この残った箇所の対応は前後関係含めて対応する必要があってちと面倒そうな印象も持っているので、いったんこの状況で公開しました。細かな実装についてはソースコードを参照ください。

便利な AppID を好みの UI で使えるメリットは大きいと思っていますので、興味ある方の参考になれば。


タイトルの通りです。obniz に赤外線人感センサー(HC-SR501)をつなげて人の存在を確認し、なんらかの変化があった場合にその結果をクラウド上の Google スプレッドシートに送信する、という仕組みを作ってみました。必要な設定やコードはこちらからも公開しています:
https://github.com/dotnsf/obniz_sensor_gas


Google スプレッドシートを複数人で共有しておけば人感センサーのセンシング結果を複数の人で共有することができます。今回の例ではそこまで実装していませんが、センシング結果にセンサーの ID を含めるようにすれば、複数のセンサーからの結果を一枚のスプレッドシートにまとめることもでき、またスプレッドシート側でピボットして確認したり、グラフ化などの視覚化も容易にできるようになります。

この仕組みはもともとこちらの動画で紹介されていたものを参考にしています。この動画ではラズベリーパイと HC-SR501 を接続して、ラズベリーパイのコマンドでセンシングを実現しています。また最終的にはそのコマンドを cron に登録する形で永続処理化まで実現されているようでした:
RaspberryPIでIoT簡単入門!クラウド対応人数カウントを作る!

2021052502


これを参考にラズベリーパイを obniz に置き換えて GPIO に接続し、ラズベリーパイのコマンドではなく、obniz のウェブページの JavaScript でセンシングし、その結果を Google スプレッドシートに向けて JavaScript で(jQuery で) POST する、となるように書き換えました。

以下、その準備段階も含めた実現の手順を紹介します(ハードウェアは持っていない場合は購入の必要があります)。

【入手するハードウェア】
obniz 本体
人感センサー(HC-SR501)
ジャンパケーブル(オス-メス)3本

【用意するソフトウェア】
・Google スプレッドシート(Google Drive から作成)


【準備作業】
人感センサーによるセンシングを行うための準備作業は以下の大きく以下3つの作業を行います:
1 スプレッドシート側の準備
2 obniz と HC-SR501 の接続
3 センシングを実行する HTML / Javascript コードの用意


1 スプレッドシート側の準備

まず Google スプレッドシートを1つ新規に作成します:
2021052601


スプレッドシートのメニューから ツール - スクリプトエディタ を選択します:
2021052602


コード画面が表示されるので、デフォルトで用意されているスクリプトをすべて消し、代わりに以下のコード(後述のデータ送信を受けて、シート内に日付時刻と送信データ内容を追加するコード)をコピー&ペーストします:
function doPost(e) {
  SpreadsheetApp.getActive().getActiveSheet().appendRow(
    [new Date(),e.postData.contents]
  );
  
  var output = ContentService.createTextOutput("ok");
  output.setMimeType(ContentService.MimeType.TEXT);
  return output;
}
2021052603



メニューから 公開 - ウェブアプリケーションとして導入 を選択します:
2021052604


プロジェクト名を聞かれたら適当に入力して OK をクリックしてください:
2021052605


続けてデプロイ設定画面になります。バージョンは "New" の "1" 、アプリケーションの実行者は自分のメールアドレスを指定、そしてアクセスできる人として "Anyone, even anonymous" を選択して deploy ボタンをクリックします:
2021052606


アプリケーションの認証が必要なので「許可を確認」をクリックします:
2021052607


ちょっとびっくりするような画面になりますがエラーではないので続けます。詳細を表示して、安全ではないページに移動します:
2021052608


作成したプロジェクトにアクセスの許可を与えるため、「許可」ボタンをクリックします:
2021052601


デプロイが行われ、最後に URL が含まれる画面になります。この URL はこの後必要になるので、メモしておきます。OK を押してダイアログを閉じます:
2021052602


これでスプレッドシートの最低限の準備はできました。必要であればスプレッドシートの名称を変更したり、他の人と共有しておいてください:
2021052603


2 obniz と HC-SR501 の接続

まず以下の作業をする前に obniz の電源を OFF にしてください。以下の作業は obniz の電源が OFF になっている状態で行ってください。

HC-SR501 には3本の接続コネクタピンがあります。接続コネクタ部分が左側にくるように向きを調整した時のピンを上から0、1、2とみなします。また obniz も同様に GPIO が下側にくるように向きを調整した時のコネクタを左から0、1、2、・・・とみなすことにします:
2021052601


この状態でジャンパケーブルを使って、0と0、1と1、2と2をそれぞれ接続します。ここまでできると写真のような状態になります:
D95C6870-EBAC-4D5B-88BB-F8F5F48F9D7F


これで obniz と HC-SR501 が接続できました。この後、obniz の電源を ON にすると HC-SR501 でセンシングできるようになり、センシングした結果を obniz から取得することができるようになりました。


3 センシングを実行する HTML / Javascript コードの用意

最後に実際にセンサーで人の存在を確認し、その結果を取得して、(上述で準備した)スプレッドシートに記録する、というプログラムを作ります。

まず obniz の開発者コンソールにログインします:
https://obniz.io/ja/console


画面左の「リポジトリ」から「新規作成」を選択します:
2021052604


新しいプログラムのダイアログが表示されます。タイプは「WebApp」、アクセスレベルは「公開」を選択し、適当なファイル名を付けて「作成」します:
2021052605


サンプルページの HTML がエディタ内に表示されます:
2021052606


この HTML をすべて消して、以下の内容をコピー&ペーストします:
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script src="https://unpkg.com/obniz@3.8.0/obniz.js"></script>
  </head>
  <body>
    <h1>人感センサー</h1>
    <div id="obniz-debug"></div>

    <script>
      var obniz = new Obniz("OBNIZ-ID");
      var sensor = null;
      const url = "https://script.google.com/macros/s/xxxxxxxxxx-xxxxxxxxxx/exec";//GASのウェブアプリケーションのURLを指定してください

      obniz.onconnect = async function () {
        sensor = obniz.wired( "Keyestudio_PIR", { signal:1, vcc:0, gnd:2 } );
        console.log( sensor );
        sensor.onchange = function( v ){
          console.log( v );
          $.post( url, { value: v } )
            .done( function( data ){ console.dir( data ); } );
        };
      };
    </script>
  </body>
</html>
2021052607


12 行目と 14 行目は変更が必要です。12 行目の new Obniz( "OBNIZ-ID" ); となっている OBNIZ-ID の部分は自分が使っている obniz の ID (電源を入れた時に画面に表示される nnnn-nnnn 形式の8桁数字)に置き換えてください。 また 14 行目の url の値は上述の作業1の最後、スプレッドシートに権限を与えた際に取得した URL 文字列に置き換えてください。

なお、17 行目で obniz と人感センサーをソフトウェア的に接続しています。上述の 0-0, 1-1, 2-2 と物理的に繋いだ作業の意味(0 が vcc 、1 がシグナル、2 がアース)を指定しています。

ここまでの変更ができたら準備はすべて完了です。


【センシング実行】
まず obniz と、obniz に接続された HC-SR501 に電源を入れます。obniz にマイクロ USB ケーブルを接続して、WiFi に接続してください。成功すると以下のように obniz ID とその QR コードが表示される画面になります:
2021052601


そして obniz コンソール画面右上の「実行」ボタンをクリックします:
2021052607


すると以下のような画面になります。緑の帯が出ていれば obniz は WiFi を経由してインターネット接続ができていることを意味しています(緑にならず、赤くなっている場合は WiFi 接続に失敗しているなど、インターネットに接続できていないことを意味しているので修正が必要です):
2021052602


この時に人感センサー近くで人が行ったり来たりして、人が存在していたりいなかったりするような状況になると、その変化を人感センサーが感知してスプレッドシートにその変化の様子が送信されます。スプレッドシートは以下のような感じになり、変化を検知する度に一行ずつ増えていきます:
2021052603

↑左が検知の日付時刻、右は value=true が人を感知した時、value=false は検知していた人がいなくなったことを検知したことを意味しています。

なお、このスプレッドシートは手動で情報を消さない限りはずっと情報が「追加」されます。一度リセットしたい場合は該当シート内に書かれた部分を手動で全選択して DELETE してください。


動作確認ができたら、いったん「終了」ボタンを押してセンシングを終了します:
2021052602


【センシングの自動化設定】
とりあえず人感センサーとしての挙動は確認できました。最後にこの仕組を(わざわざウェブページを開いて「実行」ボタンを押さなくても、電源に接続しただけで実行できるように)自動実行する方法を紹介します。

改めて obniz 開発者コンソールに戻り、左側のメニューから「サーバーレスイベント」を選んで「新規作成」します:
2021052601


適当な名前(図では「人感センサー」)を入力し、「トリガー」には obniz Hardware Event を選択後に対象 obniz id を選択して online イベントを、「実行するアプリケーション」にはリポジトリ内の HTML を選択後に先程作成した HTML を、それぞれ選択します。最後に「作成」ボタンをクリックします:
2021052602


これで対象 obniz がオンラインになったタイミングで対象 HTML ページが実行されるので、自動起動に近い処理が実現できました。いったん obniz の電源を OFF にしてから ON にするなどして、再度センシングできているかどうか(スプレッドシートに情報が追加されるかどうか)を確認してください:
2021052603


肝心のセンサー精度などはまだちゃんと測定できていない(苦笑)のと、実際には HC-SR501 についているネジでそのあたりの調整もできるようですがまだしていない(苦笑)ので、実際のところどの程度現実的に役立つものなのか、まだわかっていないところもありますが、(ラズベリーパイや)obniz があるとこんなことも簡単に作れちゃうんですね。単にセンシングするだけでなく、取得した内容をインターネット上に記録して共有するところまでできている点がポイント高いと思っています。




「LINE 手描きスタンプ」「お絵描き共有サービス」「お絵描き Slack」などのお絵描き系(そんな系あるのか?)ウェブアプリを複数開発・運用しています。

これらのウェブアプリで使っている「お絵描きパネル」 UI 部分の作りはほぼ共通です。細かいことを言い出すと i18n 対応の有無とか、履歴呼び出しの有無とか、各アプリ毎に固有の情報を保存したりしなかったりしているので UI 部分含めて全くの共通ではないのですが、でも共通パーツが多いのも事実です。

今後も新しいアプリでこの「お絵描きパネル」を使うことも何度かあるだろう、というわけで、今後のアプリ開発を楽にする目的でパネル部分のモジュール化を地味に続けていました。とりあえず公開してもいいかな、というレベルになったので公開します。

ソースコードはこちらです:
https://github.com/dotnsf/doodlejs

実際に動くサンプルはこちら(スマホか PC のウェブブラウザでアクセスしてください):
https://dotnsf.github.io/doodlejs/

2021052501


実際の UI 含めた挙動は動くサンプルのページで確認してください。2021/05/24 時点では以下のような機能を持っています:
・PC のマウスやトラックボール/Windows タッチパネル/スマホのタッチでの描画に対応
・i18n は無し(画面は日本語のみ)
・線の色、太さを指定して描画する。指定した色を背景色に指定することもできる。
・アンドゥ・リドゥ・リセット可
・「送信」ボタンを押すと、描画内容を画像化し、エンコードした結果を console.log() で書き出す(スマホだと確認できません、ごめんなさい)
・「送信」ボタンを押した時の挙動はカスタマイズ化。実際に描画データをサーバー側へ送ってバックエンドに保存する、といったこともできる(ここまで含めてサンプルコードあり)


↑特に最後のカスタマイズ機能の目処がたったので公開を決断しました。サンプルのページだけでもお絵描きの描画は体験できます。が、実際にこのモジュールを組み込んで作るアプリでは描いた絵を保存する機能も必須だと思っています。一方、Github ページでサンプル公開しているとその部分の実現が難しく、保存するようなカスタマイズができる必要性も考慮した上で試行錯誤した結果、まあまあいい感じのバランスが取れた状態で公開しています。


【使い方】
自分のウェブアプリに組み込んで使う場合は以下の作業を行ってください:

①ソースコードを git clone またはダウンロード

フロントエンドで最低限必要なファイルは doodle.js ファイルと、サンプルページである index.html の2つです。

バックエンド側のカスタマイズも行う場合は、そのサンプルとなっている app.js ファイルも必要です。

②サンプルを元にウェブページを作成

フロントエンドのサンプル UI となる index.html を参考に(あるいはこのまま使って)ウェブページを用意します。同ウェブページには以下の要素が含まれている必要があります(サンプルの index.html にはすべて含まれています):

・お絵描き用のキャンバスを含むことになる <div id="cdiv"></div>
・色選択パーツの <select id="select_color"> ~ </select>
・線の太さ選択パーツの <select id="select_linewidth"> ~ </select>
・背景色変更ボタン <button onClick="setBG();"> ~ </button>
・アンドゥボタン <button onClick="undo();"> ~ </button>
・リドゥボタン <button onClick="redo();"> ~ </button>
・リセットボタン <button onClick="resetCanvas();"> ~ </button>
・送信ボタン <button onClick="sendCanvas();"> ~ </button>

ウェブページが用意できたら、画面ロード後にそれぞれの id 値を指定して以下の JavaScript を実行します(サンプルの index.html には既に含まれています):
$( function(){
  $('#cdiv').doodlejs({
    select_color: 'select_color',
    select_linewidth: 'select_linewidth',
    undo_btn: 'undo_btn',
    redo_btn: 'redo_btn',
    setbg_btn: 'setbg_btn'
  });
});

これで用意されたパーツ要素を使って、指定された <div id="cdiv"></div> 内に連動して動く Canvas が生成されます。

③「送信」ボタンのカスタマイズ

お絵描き後に「送信」ボタンを押した時の挙動をカスタマイズできます。上記サンプルでは単にお絵描き内容を画像化→エンコードして、結果のテキストを console.log() で出力しているだけですが、バックエンドに送信するようなケースを想定してカスタマイズできるようにしています。

送信ボタンをクリックした時の挙動をカスタマイズするには DOODLEJS.prototype.postCanvas() 関数を上書きします。この関数はお絵描き内容を画像化(png 化)したデータを引数に実行されます。プロトタイプでは以下の内容になっているので、その画像をエンコードして console.log() に出力しています:
(カスタマイズ前の処理)
DOODLEJS.prototype.postCanvas = function( png ){
  console.log( 'png', png );
};

例えば画像データをサーバーに送る場合は以下のような内容にプロトタイプを上書きします:
DOODLEJS.prototype.postCanvas = function( png ){
  //. バイナリ変換
  var bin = atob( png );
  var buffer = new Uint8Array( bin.length );
  for( var i = 0; i < bin.length; i ++ ){
    buffer[i] = bin.charCodeAt( i );
  }
  var blob = new Blob( [buffer.buffer], {
    type: 'image/png'
  });

  //. フォームにして送信
  console.log( 'Sending data... : ' + blob.size );
  var formData = new FormData();
  formData.append( 'image', blob ); 
  formData.append( 'timestamp', ( new Date() ).getTime() );

  $.ajax({
    type: 'POST',
    url: '/image',
    data: formData,
    contentType: false,
    processData: false,
    success: function( data, dataType ){
      console.log( data );
    },
    error: function( jqXHR, textStatus, errorThrown ){
      console.log( textStatus + ': ' + errorThrown );
    }
  });
};

これで画像データが POST /image という REST API でポストされることになります。後はバックエンド側でこのルーティングを処理するような REST API をバックエンドに用意し、送られてくる画像データを解析するなり、保存するなり、、といった処理を実装することになります(以下は app.js に含まれるサンプルで、特に保存せずに送信された画像の情報を返すだけの内容です):
app.post( '/image', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );

  if( req.file ){
    var imgpath = req.file.path;
    var imgtype = req.file.mimetype;
    var imgsize = req.file.size;
    //var imgfilename = req.file.filename;
    //var filename = req.file.originalname;

    var timestamp = parseInt( req.body.timestamp );

    var img = fs.readFileSync( imgpath );
    var img64 = new Buffer( img ).toString( 'base64' );
    fs.unlink( imgpath, function( err ){} );

    var params = {
      path: imgpath,
      type: imgtype,
      size: imgsize,
      timestamp: timestamp
    };
    console.log( params );
    var p = JSON.stringify( params, null, 2 );
    res.write( p );
    res.end();
  }else{
    res.status( 400 );
    res.write( JSON.stringify( { status: false, error: 'not initialized.' } ) );
    res.end();
  }
});

ウェブ画面内にマウスや指でお絵描きできるパーツ部品を組み込んで、送信後のデータ処理までカスタマイズするのに便利だと思っています。よかったら使ってください。


 

このページのトップヘ