SNSでの議論をマシにするサービス開発 ~第3弾(デプロイ編)~【Golang, React】

こんにちは。

今回は「SNS上の議論をマシにできないか」と思い、開発し始めたサービスのデプロイ編で、 ↓ これらの記事の完結編です。セキュリティ編なんてなかった。。。

ume-boshi.hatenablog.jp

ume-boshi.hatenablog.jp

ume-boshi.hatenablog.jp


概要

SNSの議論の質をマシにできる可能性がある、Twitterカードを生成できるサービスをデプロイしました! 強い意志で意見しようというイメージがある「物申す」ことを直接もじって、「Mono Mosu」という名前にしました。ネーミングセンスは無いです。

mono-mosu.herokuapp.com

↓ のような画像を簡単に生成することができます!



開始当時に検討していた機能は下記の4つでしたが、そのうちDBやログイン機能を要さない1と4のみを実現させました。つまり、前記事からはUIの調節ぐらいをしました。

  • 機能1. 立場や年齢などのわずかな個人情報の提示を行うことで匿名性を弱める
  • 機能2. UIを工夫して様々な立場の存在を強制的に見せたり、発言前の忠告を設ける
  • 機能3. まとまった意見が出るように、一人2回のみ発言できるようにする
  • 機能4. 画像を用いた視覚的特徴を持たせる。


使用手順

たったの2ページしか実装していないシンプルisベストなシステムです。

  1. ホーム画面にアクセス
     一般ユーザ向けのシステムの概要が書かれています。左側の文章が右側の画像のようにまとめられるというイメージで作りました。

  2. 「カードを作成してみる」ボタンをクリック
     カードの使用イメージをホーム画面で伝えられたはずなので、実際にカードを作る画面に遷移します。

  3. フォームに個人情報を入力
     カード作成フォームの欄は、出力画像と表示がほぼ同等になるようにabsoluteな位置調節を試みました。スマホ画面でも対応できるように、レスポンシブなCSSデザインになっております。ただ、apple系のOSではUIが勝手に変化してしまい、崩れてしまう可能性はあります。あとスマホでは文字サイズがおかしくなる現象もあり。

  4. Twitter等のSNSで、発言内容に添付して投稿する!
     画像をダウンロードし、それを用いてTwitterなどで発言をするだけです。ハッシュタグをつけるのを忘れないようにしてくださいね ;)


実装内容と使用技術

本システムを実装するために使用した技術は下記項目です。これらの環境で、画像のようなシステム構成で実装しました。

  • バックエンド:Golang
  • フロントエンド:JS(React)
  • デプロイ先:Heroku*2

システム構成図2.png


追加実装内容

Backend側のGolangサーバをherokuに上げる際にポートの問題が生じるので、"httprouter”のパッケージを使用している場合は下記のように書き換えます。

func main() {
    r := Build()
    // log.Fatal(http.ListenAndServe(":8080", r))
    log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), r))
}


次に、フロントエンドについて、CSSを工夫して画面サイズをレスポンシブにしました。また、画像生成時に用いるフォームの配置も "%"を用いた位置調節に更新しました。

React側のコードの一部抜粋です。ここの入力フォームについて、見た目が改善するようにCSS側の調節を加えました。

        <div
          className="showCard"
          style={{
            marginLeft: `auto`,
            marginRight: `auto`,
            marginBottom: `2rem`,
            backgroundImage: `url(${cardBase})`,
            backgroundSize: `contain`,
            fontFamily: `ipaFont`,
            position: `relative`
          }}>

          <input name="hashtag" type="text" placeholder="タグ名を入力"
            value={this.state.hashtag}
            onChange={this.handleInputChange}
            className="hash-posi"
            id="hashInput"
          />

          <select name="age"
            value={this.state.age}
            onChange={this.handleInputChange}
            className="age-gen"
            id="ageInput"
          >
            <option value={0}>-</option>
            <option value={10}>10</option>
            <option value={20}>20</option>
            ...
          </select>
          ...
        </div>

こちらが該当箇所のCSSコードです。

@media screen and (max-width: 900px){  /*①*/
  ...
}

@media screen and (max-width: 650px){  /*①*/
  h1{
    font-size: 1.5rem;
  }
  .showCard{
    width: 288px;
    height: 162px;
  }
  
  .hash-posi{
    position: absolute;  /*②*/
    font-size: 1rem;
    width: calc(0.5556* 100%);  /*③*/
    height: calc(0.1481* 100%); 
  }
  
  #hashInput{
    left: calc(0.1528* 100%);
    top: calc(0.1481* 100%);
  }

  .age-gen{
    position: absolute;
    font-size: 0.7rem;
  }

  #ageInput{
    left:  calc(100% * 0.8264);
    top:   calc(100% * 0.1556);
    width: calc(100% * 0.125);
  }
...
}

①レスポンシブな画面サイズに対応する設定
該当箇所の書き方をすることで、max-widthの画面サイズに合わせて採用するスタイルを変更できます。この際、"max-width: 900px" > "max-width: 600px" > ... > "mix-width: 900px" という順序で記述することで、画面サイズが大きいものから対応することができます。
※max-width: 900pxが、画面サイズ900px以下のときの条件

②親要素内を基準として絶対位置を指定するための設定
フォームの親であるshowCardクラスを"relative"にし、子要素は"absolute"にしています。ただフォームだけにdisplay: absoluteを指定すると、画面全体を基準としてしまうので、親要素をrelativeにしてあげる必要がありました。

③画面サイズの変化に強い絶対位置の指定設定
calcと"%"を用いて、位置指定を容易にしました。この割合("0.5556"などの値)を算出するために、まず親要素のwidthとheightを固定し、その画面にマッチするように実数(left: 105px;など)で位置合わせをしていきます。全体の絶対位置が決まったのち、絶対位置を親要素の縦横サイズで割ってあげると導出できます。直接55.5%を指定した場合、うまくいかなかった記憶があります。


おわりに

極めて暇な人は是非使ってみてください :)
このシステムの本質は、あくまで「SNS上の議論改善」という問題提起がしたかっただけなので。

ブログの1か月連投チャレンジでは、4つほどの作品開発を並行していましたが、全部をちゃんと成し遂げるには研究活動を無下にせざるを得ませんでした。並行でダラダラと進めるよりは、1段落しそうなものをさっさと手放したいと思い、このシステムを「Mono Mosu」としてデプロイして終わらせることに。

学生中に終わらせておきたい開発物(ロボットやデバイス)がまだ大量にあり、これで心残りなく別の開発に取り掛かれそうです。

// 実際に多くの人に使ってもらえるのであれば、引き続き機能追加をしていくかもしれません。。。

ブログ1か月連投チャレンジ成功!【死ぬまでにしたいこと】

こんにちは。

先月5月を通して、技術ブログ1ヶ月連投チャレンジを達成することが出来ました!皆さんに見ていただいたおかげで、何とか続けられたんだと思います。見てくださった皆さん、ありがとうございました ;)


計画の概要

死ぬまでにしたい100のことを考えた結果、ちんたらしていたら死ぬまでに絶対に終わらないと判断したので、学生中にいくつか達成しようと考えました。そこで一番初めに取り組もうと思ったのが、「ブログ1か月連投チャレンジ」です。何かを連日で成し遂た経験が無い私には、かなりハードルが高い挑戦でした。

ume-boshi.hatenablog.jp

達成条件

まず、達成の最低条件を宣言していました。

  • 5月31日23:59時点で、合計31記事を投稿完了済み
  • 記事の順序が前後 / 変更されても問題なし
  • 記事として成り立っているならば、あとから追加・編集も可能
  • ピンチの場合、2日までの遅延はおKにしておこうか。

死ぬまでにしたいことなので、ハードルが低すぎても仕方が無いと思い下記のような記事を書こうと決意していました。

  • 技術ブログの記事である
  • 自分の行動に関連がある
  • 見る人によっては勉強 / 発想の手助けになる

投稿スケジュール

1か月間で ↓ のスケジュールで記事を投稿してきました。黄色で囲われた箇所は、計画当初には投稿予定が無かった記事です。ほとんど初期計画通りに投稿できた感じです。

f:id:ume-boshi:20210608223637j:plain
実際の投稿スケジュール

前半はおおよそ開始当初の予定通りに進んでおり、サービスの紹介記事などは書き貯めることすらできるほどに、投稿が安定していたと思います。このころは毎投稿ができていました。GWだったことも安定していた理由ですが。
2週目の木曜日からは祖母の体調悪化 + 葬式があり、1週間程度は書き貯めていた記事をほぼ全消費する形で事なきを得ていた感じですね。このあたりから記事が雑にならざるを得なくなり、読者も減った印象があります。はてなスターも付かなくなりました。記事の質が落ちてしまうため、ここで中断 / 延期をするか悩んだのですが、とりあえず続く限りは続けてみることにしました。
後半は、なかなか自身のある記事を書くことができませんでした。というのも勉強とまとめ系の記事は興味を持たれづらいことが判明していたにも関わらず、開発記事を増やすほどの時間が十分にとれなかったためです。1日で勉強できる情報量にも限界があること、AIのほうがうまく書きそうな記事に思えて自信を失ったこともありました。(karaageさんのドクペ紹介記事にそんな言葉があって、ドキッとした記憶があります。。。)後半時期は、研究室の中間発表的な時期とも重なっていました。ともかく、最終日までになんとか31記事(32記事)を投稿し終えることができました。

実は29日目の記事はズルをしていて、22.5日目の記事としてdummyを投稿しています。1時間はんだ付け実装動画。翌日にちゃんと30日目の記事を投稿したことと、基板実装に6時間以上 + エンコードに4時間かかったので、このdummy記事が手抜きだとは思いませんし心残りは微弱です。




記事振り返り

これは読んで欲しい! ~頑張った記事~

結構時間をかけて頑張った記事です。まだ見てないものがあれば、ぜひ読んでみてください!


群ロボットの基礎知識を勉強し、まとめた記事です。日本語でまとまった情報を得られるページは無く、ちょっとした群ロボットのサーベイ記事的な立場としては十分な情報量があると自負しています。
ume-boshi.hatenablog.jp


群ロボットの位置取得方法について、図付きでまとめた記事になります。↑ と同様に、日本語である程度まとまった情報が得られる資料として、初学者にとっての足がかりになるかと思います。
ume-boshi.hatenablog.jp


無駄なデジタル情報に触れる機会が多くなり、スマホにも依存する時代ですが、それらとうまく付き合うためのDigital Minimalistという概念があります。この記事では、Digital Minimalistになるために私が実践しているAndroid用のネットワーク制限設定についてまとめたものです。

かなり効果の高い方法となっており、割と本気で取り上げたのですが、反応がよろしくなかったので宣伝しておきます!
ume-boshi.hatenablog.jp


エンコーダは事前にモータとセットで購入する必要があったり、全方位移動式のロボットには採用しづらい問題がありました。本記事では、そんな条件でも使用できる後付け + オムニホイールを採用したエンコーダユニットを作った話です。手作り感は強いですが、こんな簡単なものでも回転数を読めることが伝わると幸いです!
ume-boshi.hatenablog.jp


VRを使う人には馴染み深いBase Stationの発する信号について、オリジナルの絵をふんだんに使用して説明した記事です。

普段マイコンのみしか使用していない人は、自己位置推定の手段として9軸センサを用いたりSLAMをする方法は身近だと思います。それに対し、この記事で紹介したOutside-in方式の位置推定は、組込み界隈ではあまり知られていない印象があり、ロボットへの応用なども検討してほしいと考えています。
ume-boshi.hatenablog.jp


Base Stationからの赤外線信号を受信するときに、環境光のノイズが強くて読み取れない問題がありました。対処として赤外線透過フィルタを数種類試したのですが、予想外に結構お金を使っている記事なので、その分見ていただきたいだけです。

赤外線透過フィルタの波長が40nm違うだけで、見える範囲がここまで違うのかという面白い知見にはなると思います。
ume-boshi.hatenablog.jp


人生初のProcessingを用いた可視化に挑戦しています。今までBase Stationがなにか知らなかった人にとっても、なんとなく位置トラッキングの高精度さと動作イメージが付くのかなと思っています。
ume-boshi.hatenablog.jp


見ちゃダメ! ~くそ記事 of the month~

死ぬまでにしたいこととはいえ、くそ記事と呼べるクオリティになってしまうことも多々ありました。

良いくそ記事 ↓


ひたすらに基板実装をし続けた日の記事です。写真でしか伝わらない美学がある気はします。
ume-boshi.hatenablog.jp


1つ前に紹介した記事での、実装中の動画を垂れ流しただけです。5倍速でチャカチャカ動くので、工業機械の動画とかが好きな人は、意外に見て退屈しないかもしれません。
ume-boshi.hatenablog.jp


本当のくそ記事 ↓

カテゴライズしてみた

開発系

群ロボット

自作VIVE tracker

その他


勉強系

群ロボット

自作VIVE tracker

その他


紹介系

サービス

電子工作

Digital Minimalist


空論


感想

挑戦に成功したことは理解できているのですが、あんまり達成感はありませんでした。そう考える理由は3つあります。

1つ目は、毎日ブログだけを書いていたわけではなく、葬式や研究室の中間発表など、気が紛れることが多すぎたためです。日常生活も大事にしながら、今回の企画を進められたのは並行的に進められた証拠なので嬉しいことではありますね。
2つ目は、すべての記事で満足の行くクオリティになっていないためでしょう。記事数が多くなるほど、質の悪い記事が相対的に増えていました。短期間に悪記事を大量に放出してしまったという罪悪感は拭えません。
3つ目は、実践的な深みを出せなかったことです。短時間で記事を書くためには実装に時間をかけられず、簡単箇所のみ手を出していた感じになってしまいました。少なくとも手や頭を動かしたことの価値はあったと思いますが、実装に注力できていない記事には、魅力的と言えるほどの質ではなかったと思います。


とはいえ、記事内容に関するサブ目標も達成できていたのは、達成感が無くとも誇れることです。ただの日記やURLのみを貼る低質な記事に比べ、技術ブログにおいてはまともなレベルに達していたと考えています。
そして、記事内容がすべて自分の現行動内容と関連しており、全く興味のない過去のことを共有するよりも自分らしさが生み出せました。
それに従い、勉強内容を自身で独占せずに、日本語で図を用いてイメージしやすく共有できたことや、あまりメジャーでない分野である、Digital MinimalistやOutside-in方式の位置推定などに目が向く機会を提供できたと考えています。ブログタイトルでもある「机上の空論」的なアイデアについても取り入れられたので、見る人によっては勉強 / 発送の手助けとなったのではないでしょうか。

サブ目標については、個人的に満足の行く結果でした。


「死ぬまでにしたいこと」に値するほどのしんどさではあった実感はあります。1つ挑戦をこなせたことで、今後の挑戦のハードルも下がったと思いたいですね。
// ただ、日常生活が多忙すぎた中で、睡眠不足を起こしながら無理をするほど注力すべきだったかは疑問です。


アクセス数の分析

1ヶ月、閲覧数などを毎日のように監視していましたが、悲しいぐらいに横ばいでした。底辺ブロガーの宿命か、はたまた記事の質が悪いことが定量的に示されただけなのか。考えたくもないですな。

f:id:ume-boshi:20210612015616j:plain:w450
恥ずかしながら、底辺ブロガーのままなのである。

閲覧数はずっと横ばいになっていますが、その中でも記事の見られやすさに関しては以前よりも深く知ることができました。例えば下記のようなことですね。

  • 土, 日曜は閲覧数が下がる
  • 夜中11時に記事を投稿しても当然見られない
  • すでにあるサービスを対象にしているため、サービス紹介の記事は意外に伸びる
  • タイトルの文面は結構クリックされるかに繋がっている
  • twitterスゴイ(ちょっと広まるとだいぶアクセス数が増加)

twitterには依存しないよう、できるだけ触れないようにしているのですが、SNSからの流入がない閲覧数には限界を感じてきました。正直、ちょっとした有名人にはなりたいしなぁ。


おわりに

5月中は研究を全くと言っていいほど進められていなかったため、今月は研究の方に注力していました。ずいぶん久々に記事を書く気がしますが、まだ2週間も経っていないんですね。

開発面においては未完の部分が山ほどあるため、それを順に消化していきたいと思います。順番としては、IMOFTHというtwitterカードのwebサービス → 自作VIVE tracker → 群ロボット という順でしょうか。面白いと思ってもらえるような完成形は見えているので、私の開発のやる気が出るまで待っていただけると幸いです。

それでは。

VR中の入力手法について調査してみた【31/31記事目】

こんにちは。ついに今回で1か月ブログ連投チャレンジの最終回ですね。

VR / AR中の入力手法はまだまだ研究段階で、「高速」に「誤入力」なく入力出来、「学習コスト」が低く使える実用段階の入力手法はほとんどありません。未だに、何もない空間に表示されたキーボードを指でタップする方法か、音声認識か、Bluetoothキーボードを使用する場合がほとんどではないでしょうか。。。

音声入力はまだ納得できますが、他の手法はスマートじゃないですよね。VR中だと、物理的なキーボードは、位置をトラッキングしていないと場所が分からなくなるので、現実的じゃありません。指でポチポチタップするのは、入力速度がバリ遅で、誤入力が半端ないので正直使い物になりません。

私は過去の作品である「FFKB」をVR空間に持ち込んでみたいと考えており、VR中の入力手法の既存研究については興味があります。そこで大雑把に調査し、まとめてみようと考えました。

ume-boshi.hatenablog.jp


概要

現在の研究として、入力手法は大きく3つに分かれており、「既存のコントローラのボタンを活かした方法」、「既存のVRコントローラのトラッキングを活かした方法」、「自身の体をインタフェースとする方法」があると私は考えます。既存のコントローラとは、HTC VIVE コントローラやPlayStationシリーズのDual Shockなどが考えられます。

1つ目はコントローラの2つのジョイスティック(タッチパッド)が利用されます。
2つ目はコントローラの位置・姿勢を取得して、タップしたり、ポインティングしたり、振ったりして入力します。
3つ目はコントローラを持たなくても入力できる方法です。例えば、自分の手の平をタップしたり、物理的なキーボードを操作するように机をタップする方法などがあります。

それでは、1つずつ研究例とともに見ていきたいと思います。


既存のコントローラのボタンを活かした方法

片手持ち VR コントローラのための日本語入力 UI の提案

f:id:ume-boshi:20210531232520p:plain
3手法が提案されている

1つのコントローラにおける、円形状のタッチパッドを活かした日本語入力方法です。タッチパッドを3*3格子の9分割にして、スマホのようにフリック入力する「Pointing and Flick法」と、9分割しにて母音はコントローラをひねることで表現する「Pointing and Rotation法」、外側の領域に時計状に子音を並べて、内側に4つの母音(い~お)を並べる「Dial and Touch法」の3つが提案されています。
2つ目はトラッキングとのハイブリッドですね。

このうち、日常的なスマホ使いと類似しているPF法が最も入力速度・精度が良かったらしいです。直感通りの結果ですね。日本人にフリック入力式が根付いている証拠でもありそうです。

JoyFlick: フリック入力に基づくゲームパッド向けかな文字入力手法

f:id:ume-boshi:20210531232529p:plain:w200
ジョイコンを活かした入力

WISS2020で発表されていた日本語入力手法です。これはNintendo Switchのプロコンにおける、ジョイスティック2つとBボタン、トリガー2つを利用しています。子音は右スティックで「あ~わ」までを選択可能で、左スティックが「あ~お」の母音を担当しています。子音を入力した後に、左のジョイスティックで母音を入力するようです。FFKBの入力方法と似ていますね。

50音キーボードと比較して同じ入力速度に到達したらしいです。50音キーボードってあんまり使わないのでイメージが付きづらいですね。


既存のVRコントローラのトラッキングを活かした方法

VR での画面占有を考慮した文字入力高速化の研究

f:id:ume-boshi:20210531232533p:plain
振るという発想が直感的で好み

インタラクション2021で発表されていた、日本入力手法に関する論文です。これは1つのHTC VIVEコントローラのタッチパッドとコントローラの姿勢情報を用いて入力を実現します。提案手法は外側に時計状に子音が配置・内側に時計状に母音(あ~おの5字)が配置されている「Dual Dial法」と、時計状に配置された子音を選択してからコントローラを上下左右に振る「Dial and Direction法」の2つがありました。

後者のDial and Direction法は見た感じ直感的な操作間だと思えましたが、入力速度・誤字の両者においてDual Dial法に劣ったらしいです。個人的には、魔法を唱えるときみたいな感じに楽しく入力できそうだと思い、お気に入りだったのですが。。。

Drum Keys

youtu.be

GoogleのDaydream Labsが提案する入力方法です。wiiリモコンのようなコントローラをドラムのスティックのように振り、キーをたたくようにして入力します。ちまちまポインタでキーを選択するよりは、ドラムのようにテンポよく体を動かして入力するほうが入力制度は上がりそうな気もしますね。

ただ、入力時の疲労感がたまりやすい問題と、空を叩くことによるフィードバックの無さは入力体験を下げる大きな原因となりそうです。


自身の体をインタフェースとする方法

VR環境におけるフリック入力形式インタフェースの開発

f:id:ume-boshi:20210531232524p:plain
コントローラなしでフリック入力可能

VR環境で、コントローラを持たずにフリック(日本語)入力する方法もあるようです。奥行きを活かして、ある一定位置よりも奥に指を持っていくと子音キーが選択でき、キーを長押しすると母音が出てスマホらしいフリック入力が実現できるらしい。ハンドトラッキングにはLeap Motionを使っているので、どのご家庭でも使える代物ですね。

被験者実験の結果によると、入力速度は上がったものの、誤入力率が爆上がりしたそうです。Hapticなフィードバックが得られないとそうなりますよね。あと、腕を上げたままにして、さらに指を立てたままにするのはかなり疲れそうです。

PinchType: Text Entry for Virtual and Augmented Reality Using Comfortable Thumb to Fingertip Pinches

youtu.be

facebookが提案する、コントローラを使用しない英語入力手法です。両手それぞれの、親指とそれ以外のある指で輪っかを作ることによって、1つずつ文字を入力します。これの面白いところは、輪っかを作る指がそれぞれ唯一のアルファベットと対応しておらず、AIによる推論で入力文字を後から推定することです。
例えば右手の親指 + 人差し指は「YUHJNM」の6つのアルファベットである可能性があります。「HAPPY」と入力したければ、「右親人」→「左親子」→「右親子」→「右親子」→「右親人」の順番で輪っかを作っていき、予測変換にHAPPYが出力されるという感じです。

これが珍しく感じるのは、英語には予測変換が存在しなかったからでしょうか。Oculus Questの開発元であるfacebookが公開していると、説得力がぜんぜん違いますね。とにかく一度は使ってみたいものです。

Decoding Surface Touch Typing from Hand-Tracking

youtu.be

これまたfacebookの入力手法ですね。1つ前の論文の半年後に公開されたものです。これは物理的なキーボードを押下するのと同じような動作で、キーボードなしで入力できます。PinchTypeと同様に、キー入力位置から予測変換する方式で、動画を見た感じでは日常使いしているキー入力に負けない入力速度を誇りそうです。

ただ動画ではOculus Quest2のハンドトラッキングを使用しておらず、Opti Trackという別の機器を用いる必要がありそうです。まあ、今までと入力感が全く変わらず、高速に入力できるのですから、全然問題なく実用化されそうですが。


おわりに

3つ目の「自身の体を利用する方法」は、Leap MotionやOculus Quest2などでハンドトラッキングが非常に身近な技術になってきたからなのでしょうか。今後、コントローラを用いる時代は廃れていくのかもしれませんね。

今回調べてみて驚いたことは、物理的な新しいコントローラを開発する研究が見当たらないことです。いちいちコントローラから自作する馬鹿はいないんでしょうか。邪道を突き進めume-boshiよ :)



それでは本記事まで、1か月間ブログ連投チャレンジにお付き合いいただきありがとうございました!死ぬまでにしたいことの1つ目を消化することが出来ました :)

// 最終日が肉体的にボロボロ過ぎたので、本記事は推敲+追記をバンバン加えていきたいと思います。。。

自作VIVE tracker計画【Ray可視化編】【30/31記事目】

最近、自作VIVE trackerづくりを進めています。

ume-boshi.hatenablog.jp


前回までは、ブレッドボード上に構築した回路でBase Stationからの赤外線信号を受光したり、その受光した信号のパルスからデータを読めるようにしたりしました。

今回は、同等の回路を持つ「自作基板の動作チェック」と、「信号処理を深める」こと、「Rayを可視化」することを行なったことについて記事にしていきます。


今回の完成形

基板を移動させると、画面上の緑の線(Ray)が移動していることがわかるかと思います。

youtu.be


自作基板の動作チェック

f:id:ume-boshi:20210522230753j:plain
このカッコいい基板

ume-boshi.hatenablog.jp

Seeeduinoを用いた基板で、最大6つのPhoto Diodeが接続できます。今回は1つのPhoto Diodeしか使っていませんが。2つのPhoto Diodeが使えなくなる代わりに、2つのスイッチとか1つの可変抵抗を使用することも可能です。

前回はESP32で実装していましたが、Seeeduinoにマイコンを変えたところでソースコードがほぼ変化しませんでした。


信号処理を深める

前回までは、「フェーズ開始 → Sweep信号受光」 に掛かった時間までしか計算していませんでしたが、Rayの方向を出すところまで実装しました。

f:id:ume-boshi:20210519224126p:plain
これです

//Base Stationのsweep信号を受信する箇所
//LighthouseA
if(LH[0])photo1.axisTime[0][photo1.nextAxis] = now - photo1.signalStart;
//LighthouseB
if(LH[1])photo1.axisTime[1][photo1.nextAxis] = now - photo1.signalStart;

if(digitalRead(10) == HIGH){ // ボタンスイッチを押している間のみ送信
  // Sweepの検出角度(rad)を求める
  float theta = 120 * (int(photo1.axisTime[0][0])-4000) * 3.141592 * 0.000006;
  float phi = 120 * (int(photo1.axisTime[0][1])-4000) * 3.141592 * 0.000006;
  char buf[20];
  char buf1[20];
  char buf2[20];
  dtostrf(theta,8,3,buf1); // 文字を0埋めしたかったが効いていない
  dtostrf(phi,8,3,buf2);
  sprintf(buf, "%s,%s\n",buf1,buf2); // 送信文字列を作成
  Serial.print(buf); // 送信
}


Rayの可視化

処理の流れとしては、まずSeeeduinoから115200bpsの速度でSerial通信し、その値をProcessingで1文字ずつ受け取ります。そのメッセージから数値を解析し、x軸に関するSweepのtheta角と、y軸に関するSweepのphi角を取得します。そして、この2角度をもとにRayの方向ベクトルを算出し、画面上にlineとして出力している形です。

f:id:ume-boshi:20210519224746p:plain
Rayの方向ベクトルを求める方法

import processing.serial.*;
Serial port;

float theta = 0;
float phi = 0;
int in_data;
int[] datas = new int[100]; //Seeeduinoからのメッセージ受け取り用

void setup() {
  size(1000, 1000, P3D); //P3Dライブラリを使う
  port = new Serial(this, "COM4", 115200); // Seeeduinoの接続先
  background(0);
  noFill();
  stroke(255);
}

void draw() {
  textSize(80);
  int boxSize = int(width * 0.5);
  // 描画エリア設定
  if (frameCount%1 == 0) {
    // 方向ベクトルを求める
    float xdeg =  -1 * cos(theta)*sin(phi);
    float ydeg =  sin(theta)*cos(phi);
    float zdeg =  -1 * cos(theta)*cos(phi);

    background(0);
    noFill();
    stroke(255);
    text(theta, 20, 80);
    text(phi, 20, 160);

    // Rayが見やすい角度に画面を回転しboxを描画
    translate(width/2, height/2);
    rotateX(-PI / 8.0);
    rotateY(PI / 2.0);
    box(boxSize);

    // Rayの始点を分かりやすくするための対角線
    line(boxSize/2, -boxSize/2, -boxSize/2, -boxSize/2, boxSize/2, -boxSize/2);
    line(boxSize/2, boxSize/2, -boxSize/2, -boxSize/2, -boxSize/2, -boxSize/2);
    
    // Rayの描画
    translate(0, 0, -boxSize/2);
    stroke(0, 255, 0);
    line(0, 0, 0, xdeg*(boxSize/zdeg), ydeg*(boxSize/zdeg), boxSize);

    // Rayの先端に丸を描く
    translate(0, 0, boxSize);
    stroke(0, 255, 255);
    ellipse(xdeg*(boxSize/zdeg), ydeg*(boxSize/zdeg), 10, 10);
  }

  if (port.available() > 0 ) {
    int count = 0;
    int minus = 1;
    int xy = 0;
    theta = 0;
    phi = 0;

    // 馬鹿みたいに1文字ずつ受信して、解析したぞ!
    while (port.available() > 0) {
      in_data = port.read();

      if (in_data == 13) { //\r
        continue;
      } else if (in_data == 10) { //\n
        break;
      } else {
        switch(in_data) {
        case 32:count++; break;//" "
        case 43:count++; break;//+
        case 44:xy=1; theta=theta*minus; minus=1; break;//,
        case 45:count++; minus=-1; break;//-
        case 46:count++; break;//,
        default://print((in_data-48)); datas[count]=in_data; 
          if (xy == 0) theta += (in_data-48)*(pow(10, (3-count)));
          else phi += (in_data-48)*(pow(10, (11-count)));
          count++; 
          break;
    }}}
    phi=phi*minus;
}}

「//Rayの描画」では、方向ベクトルをlineとして出力したのですが、長さは対岸のBoxの面までの長さにしています。このために、Z軸が座標boxSizeになるように、xとyの値を(boxSize/zdeg)をかけることで調節しています。

「//Rayの先端に丸を描く」については、translateによって自分の位置(?)を右側の面に持ってきています。残りはただ円をellipse()関数で描画しただけです。

「// 馬鹿みたいに1文字ずつ受信して、解析したぞ!」の箇所は、"port.readStringUntil('\n');"という関数を知らなかったため生み出された糞コードです。皆さんは文字列を1行ずつ読み、型変換をうまく活用して楽に読み取ってくださいね :)


おわりに

人生で初めてのProcessingでしたが、環境構築から描画処理の実装まで2時間程度しかかからず、ものすごく簡単な印象を受けました。

昔からwatakoさんの可視化がかっこいいなと思って、そのうち触ってみようと思っていたのですが、もっと早く手を出していればよかったと後悔しています。karaageさんみたいに、XJにも手を出してみたい。。。

今度からは、ロボットの内部情報をバンバンと可視化させていきますよ。


参考文献

【ESP32】自作ソフトレーザーポインタの使い勝手を向上させた【29/31記事目】

今回は、1年以上前の作品について、ちょっとブラッシュアップした話です。

機能改善したのは ↓ の記事にある、ソフトウェアレーザポインタです。

ume-boshi.hatenablog.jp


前回の状態

↓ の動画の状態ですね。

youtu.be

これは赤い自作基板ですが、今回は2つバージョンが上の青い自作基板で実装しました。


変更点

1つ目の変更点として、基板を更新したことでピン配置が昔と変わっており、動作確認からコードの修正を行いました。そして、ポインタの動作方法について更新しました。

前者は生地にすることが無いので、今回は後者のポインタの動作方法について紹介したいと思います。

前回の動作方法と更新後の動作方法

前回は、初期姿勢を基準として、Roll角とPitch角の変位をそのままマウスカーソルの移動速度に反映していました。そのため、カーソルの移動を止めるためには、ポインタを正確に元の角度に保つ必要がありました。持ち方は、マイコン(センサ)が手のひらの中心に来るようにし、茶碗を持つときのような動きで操作する必要がありました。角度によって移動速度が変化するため、手を静止していたとしても、全速でカーソルが動くこともしばしば。。。

ついでに、ボタンを押していない間もカーソルが移動するようになっており、結構不便でした。

f:id:ume-boshi:20210530092907p:plain
前回の動作方法


今回は、オイラー角を用いることまでは一緒なのですが、角度をそのまま反映させるのではなく、「角速度」を利用しています。そして更に、角速度を直接PitchとYawから求めるのではなく、仮想的な持ち手を定義して棒を振り回す感じで扱えるようにしました。仮想的な持ち手を定義したことにより、操作量に対しての移動距離が現実とおおよそ対応付けられるようになり 、操作性が向上しています。

角速度を用いていることにより、ポインタに速度が加わらない限りはカーソルも移動しません。また、ボタンを押していない間はポインタが動かない + パワポの「ソフトポインタモード」も切るようにしており、より使いやすくなっています。

f:id:ume-boshi:20210530092913p:plain
今回の動作方法


実装

#include <MadgwickAHRS.h>

#include "IoTBoardUtility.h"  // 自作基板用初期設定ライブラリ
#include "IoTDisplay.h"  // 自作基板用ディスプレイ操作ライブラリ
#include "BMX055.h"  // BMX055の自作ライブラリ。今回は初期化ぐらいしか使っていない。
#include "BleMouse.h" //https://github.com/T-vK/ESP32-BLE-Mouse

Madgwick MadgwickFilter;  // ①センサの誤差を確率論的に減らしてくれるフィルタ
BleMouse bleMouse;

int count=0;
unsigned long startTime=0;
unsigned long timer=0;
unsigned long presenTimer=0;
bool timerFlag = false;
bool enableSoftMouse = false; // 昔はボタンを1度押したらカーソルが常に動くようになっていた。その名残。
bool softPointerFlag = false; // ソフトポインタモードにするか、ハードのレーザポインタ素子にするか。
bool redFlag = false; // パワポの Ctrl+L を押した状態にする
float diffroll=0.0, diffpitch=0.0;
float preA[3] = {0,0,0};
float diffA0,diffA2; 


void setup() {
  Serial.begin(115200);
  init();
  initBMX055();
  initDisplay();
  timer=millis();

  bleMouse.begin(); // マウスの機能のみでパワポ操作を全て行なう

  if(digitalRead(DIP4)==HIGH){
    softPointerFlag=true; // あるスイッチがHIGHのとき、ソフトレーザポインタをON
  }

   MadgwickFilter.begin(20); //周波数
}


void loop() {  
  enableSoftMouse=true; // 

  if(bleMouse.isConnected()) {
    digitalWrite(LED1,LOW);
  }

  if(bleMouse.isConnected() && softPointerFlag){ // ②
    if(digitalRead(SW3)==HIGH && redFlag){ // ボタンを離したタイミングでredを消す
      bleMouse.click(MOUSE_FORWARD);
      redFlag=false;
    }

    if(digitalRead(SW3)==LOW  && !redFlag){ // ボタンを押したタイミングでredを付ける
      Serial.println("red");
      bleMouse.click(MOUSE_FORWARD);
  
      redFlag=true;
    }

    if(enableSoftMouse && (millis()-timer > 50)){
      float dx, dy;
      int len = 8;

      if(!digitalRead(SW3)){
        getValues();

        // Gyro, Acclm Mag配列と、r,p,y,prehogehogeは BMX055.hでfloatで定義している。。。
        MadgwickFilter.update(Gyro[0],Gyro[1],Gyro[2],Accl[0],Accl[1],Accl[2],Mag[0],Mag[1],Mag[2]);
        r = MadgwickFilter.getRoll();
        p = MadgwickFilter.getPitch();
        y = MadgwickFilter.getYaw();

        dx = -atan2((y-preyaw)*4, len)*rad2deg;  // 仮想的な持ち手を基準に角速度を求める
        dy =  atan2((p-prepitch)*4, len)*rad2deg;

        Serial.printf("x:%f\ty:%f\n",dx, dy);
        bleMouse.move((int)(dx),(int)(dy));
        preroll = r;  prepitch = p;  preyaw = y; //過去の角度として保存
      }
      timer = millis();
    }
  }

  if(digitalRead(SW2)==LOW && bleMouse.isConnected()){ //戻る(略)
  if(digitalRead(SW4)==LOW && bleMouse.isConnected()){ //進む(略)
  if(digitalRead(SW1)==LOW){ //時間計測開始(略)
  if(timerFlag && (millis()-presenTimer > 1000)){ //450ms 間隔で液晶を更新(略)
}

事前条件として「X-Mouse Button Control」というソフトを用いて、windows側でマウスの設定を変えています。私はForwardボタンを日常使用していないため、Forwardボタン(5つ目)をCTRL + L に対応付けてました。

①. Madgwickフィルタを用いているためyaw角の補正が入り、結果として静止時にx軸方向の移動が安定しない問題が残っていますが、個人的には現在ので割と満足です。操作しづらい場合は、変にyaw角の取得にこだわらずに、ジャイロセンサのz軸周りの回転速度を取るだけでもいいかもしれないですね。

②. 今回のメイン改造箇所です。3つのif文がありますが、それぞれ上から「ボタンが離された瞬間、カーソルを通常の➩形状に戻す」「ボタンが押された瞬間、カーソルをポインタ形状🔴にする」「ボタンが押されている間はマウスカーソルを動かす」処理をしています。
getValues( )関数で、Gyro, Accl, Magの9値を更新しており、それをMadgwickフィルタに突っ込んでいます。Madgwickした結果得られたPitchとYaw角を使用し、一度それぞれの角速度を求めています。
そして、角速度を適当に引き延ばしたものと(雑)仮想的な持ち手長"len"を用いて、arctanを計算することで棒を振り回すように、直感的な操作ができるようになっています。


完成品

youtu.be


おわりに

私は、基本的には一度完成したと考えた作品については、あまり改善をほどこさない人間でした。今回、1年以上前のプログラムを引っ張り出して修正してみて、どれだけ意味不明か怖かったのですが、短いコードゆえに意外に苦労しませんでした。システムの改善も意外に楽しいですねぇ。

自作のBMX055用のライブラリが間違っていたっぽくて、今まで正常にroll, pitch, yaw角が取れていませんでした。今回はMadgwickフィルタで手抜きしましたが、今度もう一度修正したいところです。

それでは。