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

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

2020 年最初のブログとなりました。本年もよろしくおねがいします。


自分が作って公開するアプリの中にスマホのモーションセンサーを使うものが少なからず存在しています。モーションセンサーとはスマホの傾きや加速度を3軸(3次元)で取得するもので、この値を取得することで「スマホは今どんな姿勢なのか?(まっすぐなのか、傾いているのか、傾いているとしたらどの方向にどのくらい傾いているのか、・・)またどのような挙動をしているのか?(止まっているのか、動いているのか、動いているとしたらどの方向にどのような加速度を持って動いているのか、・・)」といった情報を取得することができ、これによってスマホをコントローラーのように扱うことができるようになるものです(例: 左に傾けたら左矢印が押された時と同じ挙動にする、など)。いろいろ面倒なネイティブアプリケーションを作らなくても、ウェブの(HTML と JavaScript の)アプリケーションでもセンサー情報が取得ができて VR アプリケーションみたいなものも作れる、といった便利さがありました。

ただ最近になって、少し前に作ったモーションセンサー対応アプリが現在のスマホでは動かなくなっていることに気づきました。これはデバイスそのものの問題ではなく、OS(iOS や Android)側のセキュリティ強化によるものが原因でした。昔の OS を使っている場合はセンサーがそのまま動くけど、新しい OS を使っていると同様には動かない、といった現象に遭遇するわけです。

これはまあ OS 提供側の事情もあるし、動かなくなったからといって不満ばかり言っていても動くようになるわけでもありません。今後の変更も含めてアプリケーション提供側が対応していくしかないのかなあ、、と半ばあきらめています。

#ただ1つだけ不満を言わせてもらうと、Android はともかく iOS 側の変更の頻度が高すぎて「やってられん!」という気になってしまうのも事実です。 (^^;


といった背景の中で、とりあえず 2020 年1月時点での OS(とバージョン)別モーションセンサー有効化方法をまとめてみました。


【OS/バージョン別対応策】

詳細は後述しますが、対応策の一覧はこちらになります:
OSバージョン対応策
Android9 以下(全バージョン)不要
以下の【モーションセンサーから情報を取得する JavaScript】の内容を実装すれば取得できる
iOS12.1 以下
12.2 以上 13 未満Safari の設定を変更
13 以上アプリケーション側で requestPermission を実行するよう実装し、ユーザーに「許可」させる



【モーションセンサーから情報を取得する JavaScript】
まず全ての OS やバージョンに関係なく、モーションセンサーから情報を取得する JavaScript の実装を紹介します(iOS 12.2 以上の場合は、この JavaScript を実行する前に後述の対応策が必要となります)。

例えばデバイスの姿勢(3次元軸での向き)を取得するには DeviceOrientation オブジェクトを使います(デバイスの挙動・加速度を取得するには DeviceMotion オブジェクトを使いますが、内容は同様なので省略します)。このオブジェクトはジャイロセンサー搭載マシンのブラウザでは有効になっています(ノート PC などジャイロセンサー非搭載機では無効です)。

この DeviceOrientation オブジェクトから値を取得するには deviceorientation イベントに対するハンドラを定義し、ハンドラ内で値を取得する必要があります。具体的には以下のようになります:
  :

//. DeviceOrientationEvent オブジェクトが有効な環境か? をチェック
if( window.DeviceOrientationEvent ){
  //. DeviceOrientationEvent オブジェクトが有効な場合のみ、deviceorientation イベント発生時に deviceOrientaion 関数がハンドリングするよう登録
  window.addEventListener( "deviceorientation", deviceOrientation );
}
  :

//. deviceorientation イベントハンドラ
function deviceOrientation( e ){
  //. 通常の処理を無効にする
  e.preventDefault();

  //. スマホの向きを取得
  var dir = e.alpha;   //. 北極方向に対する向きの角度
  var fb = e.beta;      //. 前後の傾き角度
  var lr = e.gamma;  //. 左右の傾き角度

    :
}

  :

まず window.DeviceOrientationEvent というオブジェクトが有効かどうか(モーションセンサー対応デバイスかどうか)をチェックします。有効な場合はデバイスから定期的に deviceorientaion イベントが発呼されるので、そのイベントが発生した時に呼ばれる関数(ハンドラ) : deviceOrientation() を定義しておきます。

deviceOrientation() 関数はハンドラが呼ばれた時点での情報を持つオブジェクト e を引数として実行されますが、このオブジェクトは3つの要素を持っています。e.alpha が北極方法に対するスマホの向き、e.beta が前後の傾き、そして e.gamma が左右の傾きで、全て角度(単位は度)の値が含まれています。これらの値を取り出すことで、ハンドラが呼ばれたタイミングでのこれらの角度を知ることができるようになる、というものです。なお、deviceOrientation() 関数は1秒に数十回呼び出されます。かなり細かい頻度でデバイスの動きを知ることができるようになっています。


上述の表でも説明しましたが、Android および iOS 12.1 以下であれば上記 JavaScript が記述されたページを HTTPS で開けばそのまま実行できてセンサー値を取得することができるようになります。

iOS 12.2 以上の場合は上記 JavaScript が実行される前に準備的な段階が必要になります。iOS 13 未満か以上かで準備段階の内容が異なるため注意が必要です。


【iOS 12.2 以上 13 未満での事前準備】
iOS 12.2 以上 13 未満の場合(つまり 12.x で x が2以上の場合)はデフォルトで Safari からセンサー値を取得することができないように設定されているため、この設定を変更しておく必要があります。

具体的には iOS の設定 - Safari に「モーションと画面の向きのアクセス」という項目があります。デフォルトではこの設定は OFF になっているはずですが、ここを ON に(緑色になるように)変更しておく必要があります:
2020011001


この変更をしておくだけで、後は Safari を開いて上述の JavaScript が含まれるページを開けば正しく実行され、センサー値を取得することができます。


【iOS 13 以上での事前準備】
さて問題の iOS 13 以上(現在 iOS を普通にアップデートするとこの状態になります)でのケースです。この環境下では前述のような Safari の設定は不要ですが、代わりにユーザーの許可無しに Safari からセンサー値を取得することができないように設定されています。なおこの許可はウェブページごとに許可する必要があるため、事前に Safari で全ページ向けの設定をしておく、ということ自体ができなくなりました。

またセンサー値を取得するページ(の JavaScript)側も個別にユーザーの許可を得るための JavaScript コードを記述して対応する必要があります:
2020011002
(↑このダイアログを出して、「許可」が選ばれないとセンサー値は取得できない)


しかも(まだありますw)この「個別にユーザーの許可を得る」タイミングも面倒です。サービスを提供する側としては「該当の URL を指定してページを開くと同時にユーザーの許可を得たい」と思うわけですが、これが許されていません(汗)。該当ページを開ききって、ユーザーがそのページの中にあるボタンをタップしたタイミングで許可を得るためのダイアログを表示し、許可された場合のみ DeviceOrientationEvent のイベントハンドラが有効になる、という仕様に変わったのでした。正直、開発・運用する側としてはかなり面倒な仕様になってしまいました。。


具体的なコードは以下のようになります。青字部分が元のコードからの変更箇所です:
  :

function ClickRequestDeviceSensor(){
  //. ユーザーに「許可」を求めるダイアログを表示
  DeviceOrientationEvent.requestPermission().then( function( response ){
    if( response === 'granted' ){
      //. 許可された場合のみイベントハンドラを追加できる
      window.addEventListener( "deviceorientation", deviceOrientation );
      //. 画面上部のボタンを消す
      $('#sensorrequest').css( 'display', 'none' );
    }
  }).catch( function( e ){
    console.log( e );
  });
}

//. DeviceOrientationEvent オブジェクトが有効な環境か? をチェック
if( window.DeviceOrientationEvent ){
  //. iOS13 以上であれば DeviceOrientationEvent.requestPermission 関数が定義されているので、ここで条件分岐
  if( DeviceOrientationEvent.requestPermission && typeof DeviceOrientationEvent.requestPermission === 'function' ){
    //. iOS 13 以上の場合、
    //. 画面上部に「センサーの有効化」ボタンを追加
    var banner = '<div  style="z-index: 1; position: absolute; width: 100%; background-color: rgb(0, 0, 0);" onclick="ClickRequestDeviceSensor();" id="sensorrequest"><p style="color: rgb(0, 0, 255);">センサーの有効化</p></div>';
    $('body').prepend( banner );
  }else{
    //. Android または iOS 13 未満の場合、
    //. DeviceOrientationEvent オブジェクトが有効な場合のみ、deviceorientation イベント発生時に deviceOrientaion 関数がハンドリングするよう登録
    window.addEventListener( "deviceorientation", deviceOrientation );
  }
}
  :

//. deviceorientation イベントハンドラ
function deviceOrientation( e ){
  //. 通常の処理を無効にする
  e.preventDefault();

  //. スマホの向きを取得
  var dir = e.alpha;   //. 北極方向に対する向きの角度
  var fb = e.beta;      //. 前後の傾き角度
  var lr = e.gamma;  //. 左右の傾き角度

    :
}

  :

ユーザーにセンサーデータ取得の許可を求めるには DeviceOrientationEvent オブジェクトの requestPermission() 関数を実行します(この関数は iOS13 以降でのみ有効なので、iOS13 未満や Android では実行できません)。そしてそのダイアログで「許可」が選ばれた場合のみイベントハンドラを有効にすることができるようになるので、addEventHander で deviceOrientation() 関数を登録することでセンサー値を取得することができるようになる、というものです。

また requestPermission() 関数は全ての画面がロードされきった後でのみ実行できます。そのため、まず iOS13 以降かどうかを判断し、そうであった場合の画面ロード時にはセンサー値取得の許可を求めるためのダイアログを表示するボタンを用意し、そのボタンがタップされたタイミングで DeviceOrientationEvent.requestPermission() を実行して許可を求めるダイアログを表示して、「許可」が選ばれた場合は deviceOrientaion() 関数を有効にする、という順序でセンサー値を取得しています:
2020011002



・・・というわけで、特に iOS 13 以降で面倒になったブラウザでのモーションセンサー有効化を手順を含めて解説しました。現実問題としてはここまで実装した上でインターネット上のサーバーに公開してはじめて動かすことができるようになるまでも大変だし、モバイルブラウザだとデバッグも大変なので超面倒だなあ、という印象です。 ただ近い将来にこの機能を使って実装したサービスを公開するつもりでいるので、事前にややこしい所だけを説明しておく目的でこのブログエントリを作ったのでした。

コンテナオーケストレーション環境である kubernetes(以下 k8s)上で PC-DOS エミュレーターである DOSBOX を動かしてみました。

なお、以下で紹介する k8s の環境としては以前にこのブログで紹介した minikube 環境を使います。minikube の(Windows 10 + WSL 向けの)環境構築手順はこちらです:
Windows 10 に minikube を導入して WSL から利用する


また k8s にデプロイする DOSBOX のイメージは Docker Hub に公開されていたこれを使うことにします:
DOSBox for Docker Server


上記ページによると、このイメージは以下2つの特徴がある模様です:
(1)起動後、5901 番ポートで VNC 接続を待ち受ける(つまり VNC 経由で DOSBOX に接続する)
(2)VNC のパスワードは環境変数 VNCPASSWORD で指定する


ではこのことを理解した上で、まずはイメージを k8s 環境へデプロイします:

環境変数 VNCPASSWORD=P@ssw0rd を指定して jgoerzen/dosbox を k8s に dosbox という名前でデプロイ
$ kubectl run dosbox --image=jgoerzen/dosbox --env=VNCPASSWORD=P@ssw0rd

kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/dosbox created

dosbox のポート 5901 を expose
$ kubectl expose deployment dosbox --type="NodePort" --port=5901

service/dosbox exposed

これでイメージのデプロイは完了し、利用可能になっているはずです。最後に利用するために必要な情報を確認しておきます:

IP アドレスを確認
$ kubectl cluster-info

Kubernetes master is running at https://192.168.99.100:8443
KubeDNS is running at https://192.168.99.100:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.


外部公開ポート番号を確認
$ kubectl get svc

NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
dosbox       NodePort    10.105.213.111                 5901:32286/TCP   9s
kubernetes   ClusterIP   10.96.0.1                      443/TCP          103d

上記の結果から 192.168.99.100:32286 で VNC 接続します:
2019122002


接続時にパスワードを聞かれます。デプロイ時に環境変数 VNCPASSWORD で指定した文字列(上記の場合は P@ssw0rd)を入力します:
2019122003


正しく接続できると xterm のターミナルと、DOSBOX アプリが起動した X Window が表示され、DOSBOX が利用できるようになります:
2019122001


これで DOS 環境を共有するハードルを1つ超えた、かも!


このブログは Node-RED Advent Calendar 2019 に参加しています。12/22 ぶんとしてエントリーしています。


今年 2019 年に新たな試みとして挑戦したことの1つに「ボランティアで IT 勉強会を開催する」がありました。それまでにも社内外の技術者向けコミュニティで講演したり、参加した先のイベント内でゲリラ的に活動するなどは行っていました。ただ前提として IT 技術者を対象としていたり、そういった知識を持った人を対象とすることがほとんどでした。一方で IT や AI 、プログラミングが少しずつ世の中で広まっている実感もあって、そんな乖離について疑問とまでは言いませんが、より多くの人に広める活動はできないだろうか、と考えることはありました。そんな中で女性向けコミュニティや、以下で紹介する子供向け勉強会を開催させていただく機会がありました。その活動についての報告を兼ねてブログにまとめてみました。


【IT勉強会について】
この手の IT 勉強会を開催する上で会場の確保は1つの壁になります。加えてパソコンそのものは持ってきてもらうとして、その電源やプロジェクターの確保、無線 LAN の有無、机や椅子の用意、そして会場を借りる上でのコスト、加えて集客方法や会場を利用する上での制約事項等を総合的に考慮する必要があります。以下で紹介する勉強会では 2016 年に千葉県市川市にできたシェアスペース「にわにわ」をお借りして開催しました:
2019121001


簡単に説明すると無線 LAN や電源の利用可能なレンタルスペースです。トップページを見ていただくとわかるのですが、普段から地域コミュニケーションを中心とした(IT に限らない)活動をされていて、親子向けワークショップも多く開催されています(画像はトップページより)。価格も非常にリーズナブル!:
800


この会場をお借りし、今年は5月に開催したのを皮切りに計5回のIT勉強会を行いました。「IT勉強会」と銘打ってますが、基本的には IBM Cloud を使った勉強会という位置づけで、IBM Cloud コミュニティのメンバーにお手伝いをいただいています。なお(*)印がついているものは Node-RED を使ったハンズオンです:
日付タイトル内容
5/19【子連れも】BMXUG市川IT勉強会「LINEでお絵描きメッセージを送ってみよう♪」【学生も歓迎】LINE LIFF を使ったお絵描きスタンプを作る
7/13【こどもも】BMXUG市川IT勉強会「テトリスを作ってスマホで振って遊ぼう!」【おとなも】(*)IBM Watson IoT サービスを使ってゲームをリモートコントロール
8/24【こどもも】BMXUG市川IT勉強会 AI先生と外国語で会話しよう!【おとなも】IBM Watson Translator, Speech to Text, Text to Speech を使って外国語会話レッスンアプリを作る
10/19BMXUG市川IT勉強会「レースゲームを作って、鈴鹿サーキットを走ってみよう!」(*)OpenStreetMap と IBM Watson IoT サービスを使った地図ゲーム、二人協力プレイ可
12/7BMXUG 市川IT勉強会番外編 【おとなも】『スクラッチ』でゲームを作ろう!【こどもも】スクラッチを使ったおにごっこゲーム開発(コピペ一切なし)


つまり今年は5回の勉強会を開催して、そのうち2回で Node-RED を使いました。また全てのタイトルに【おとなも】【こどもも】と含まれているように、対象者の前提は設けていません。もともとこの会場で行われるイベントは両親がお子さんと一緒に参加されたり、お年寄りの方が参加されたりすることが多かったこともあって、その一環で参加していただけたら・・という思いもありました。結果的に「普段キーボード入力を使う機会が少ない(コピペして、すらそのままでは通じない)」ようなお子さんや大人の方に多く参加いただけたと思っています。この点では当初の目的がある程度達成できたと感じています。


なお勉強会ではないのですが、7/13 開催の内容については 7/18 に開催された Node-RED Con Tokyo 2019 の中でもレポートする形で紹介しています:
IBM Watson IoT を用いた遠隔ゲームコントローラーと Node-RED による簡易カスタマイズによる実現(*)


※個人的にはこれら以外でも IT 関連のボランティア活動は行っていましたが、このブログでは上記イベントに限ったレポートとさせていただきます。


【勉強会で大変だったこと】
大変だったこと、というか大変なこと。結論を最初に言うと「時間との戦い」です。

参加者は「お子さんと、その親」が大半でした。ここでの「お子さん」とは、下は小学校低学年から、上は高校生がいたかなあ・・・中学生はいました、といった感じ。おそらくスマホは僕以上に使いこなせるのでしょうが、PC を普段から使っているわけではありません。ブラインドタッチなど以ての外、「コピペ」が「Ctrl+C して Ctrl+V」と理解できるわけでもありません。「選んで、右クリックして、コピーして、・・・」と教える必要があるレベルです。PC 操作には普段の勉強会やハンズオンとは比較にならないほどの時間を要します。

加えて、全ての勉強会がハンズオン形式となっていて最終的にアプリを1つ作りあげる(!)のですが、だいたい2時間弱で作れるものを用意する必要があります。時間をかければ細かい説明もできるし、作ったものを自分の好みに合わせて改良できたりもするんですが、一方で時間が長くなって「途中で飽きられちゃう」と勉強会としては致命傷です。もうPCの前に戻ってきてくれなくなります(苦笑)。コツとしては最初にアプリのデモを見せて(つまり完成形を見せて)、ある程度興味を持ってもらって、「頑張ればあのゲームができる!」と思わせて(笑)、なんとか集中力を2時間キープしてもらう、加えて神様に祈るw! そして目的のアプリを最短コースで作り上げる!! という持久走のような2時間になります(あ、でも苦痛ではなく楽しいですw)。

勉強会で扱うハンズオンのコンテンツはベースとなるものがあったりなかったりですが、ベースがあってもこの勉強会向けの改良を行っています。理由は「そのままでは理解できない」と思われる内容があったり、キーボード入力が多すぎて着いてこれないと思われたからです。基本プログラミングなので、プログラミングの入力は必要なのですが、その内容が多すぎると集中力が途切れてしまう懸念があります。かと言ってコピペのオンパレードだと「いま自分は何をコピペしてるのか」「そもそもこれはどの部分を作っているのか」がわからず惰性の作業になってしまう(要するに勉強会にならない)恐れもあります。一方で「ある程度プログラミングっぽいことが体験できるようにしたい」とか「苦労するから完成すると嬉しい」というユーザー体験的な観点からはやっぱりプログラミングが体験できるようにもしたくて・・・ このあたりのバランスが非常に難しいのでした。実際、今も改良を続けながらコンテンツを準備しています。

コンテンツのアイデア出しやコンテンツ作りにも時間は必要だし、2時間で一通りの解説と動作確認ができるように仕上げないといけないし、毎回が色んな意味で時間との戦いになっています。


【Node-RED を使った感想】
上述のように、5回の勉強会のうち2回で Node-RED を使ったハンズオン開発を行いました。ちなみに他の2回は Node.js (のコードを github からコピーして使用)、1回は Scratch を使いました。また Node-RED を使う場合も HTML ページを記述するテンプレートノードの内容についてだけは(時間の都合もあって)コピー&ペーストで逃げました。今にして思えば Node.js の2回は Node-RED で作れるようにしておけばよかったと感じています。

プログラミング教育のハンズオンで Node-RED を使う場合のメリットは、なんといっても「少しずつ作りながら&動かしながら進めていける」ことだと思っています。ノードの説明をして、それらを組み合わせて1つのフローを作っては、作ったフローを実際に動かして動作を確認する、これを繰り返すことで機能単位で少しずつ動作を確認しながら作ろうとしているものが完成体に近づいていく様子を体験できるようになります。この「少しずつ完成に近づいていく」のがプログラミングの醍醐味であり、楽しい部分でもあると思っています。

また Node.js を使う場合と比較すると、「コマンドプロンプトやターミナルからコマンドを実行する」に相当する作業がなく、開発作業に集中できるという点も大きなメリットであると感じています。プログラミングといいつつ、最終的にサーバー上で動かすため各種 Linux コマンドを使わずに進めていくのは困難なのですが、Node-RED だとそのあたりはよく作られていて Node-RED の画面内だけでアプリを開発し、サーバー上にデプロイすることまでができます。Windows / Mac の環境依存になる部分も少ないため、教える側としては非常に楽になる、といった、副作用的な効果もありました。特に今回のように教わる側の知識があまり高くない場合では大きなメリットのあるハンズオン環境といえると思います。

似たような GUI プログラミング環境として Scratch もあります。こちらははじめからウェブサービスとして提供されている点と、画面を確認しながら作れるという点で視覚的に非常にわかりやすい環境といえます。一方で外部とのデータやりとりについてはどうしても弱い点があるようにも思えます(逆にそういうやりとりが必要なければ気にすることもないと思ってます)。この外部とのデータのやりとりについては Node-RED が得意としている分野でもあるので、このあたりが今後の使い分けの基準にもなっていくんだろうなあ、と感じています。


【今後の話】
今年の5回の勉強会を経て、比較的 IT 知識のない人(普段、ワードやエクセルさえ使う機会のないレベルの人)向けの学習環境としての Node-RED の優位性を改めて感じることができました。一応、この活動は来年以降も続けるつもりでいて、2月初旬に予定している年明け一回目のコンテンツでも使う想定をしています。

ただし、来年は「Node-RED を使う前提でコンテンツを考える」のではなく、「参加者が興味を持ちそうな(エンターテイメント性のある)コンテンツを作って」&「それを Node-RED で実現できるように改良する」という準備をしていこうと思っています。ぶっちゃけ手間がかかるといえばかかるのですが、この準備自体が自分の勉強にもなるし、そういうことが実現できる Node-RED のポテンシャルの高さを証明することにもなるのではないか、と思っています。



このページのトップヘ