TinyPilot: Month 2

Michael Lynch

TinyPilot:2か月目

一言まとめ

TinyPilotで大きな想定外のトラブルに見舞われましたが、着実に前進を続けています。

ハイライト

  • 設計上の問題に対応するため、TinyPilotの販売を一時停止しました。
  • TinyPilot用のカスタムUSB電源コネクタを製造しています。
  • すべてのプロジェクトを合わせた収益は過去最高水準に迫る合計3,600ドルでした。

目標の振り返り

毎月のはじめに達成したい目標を掲げています。目標に対する結果は次のとおりです。

TinyPilotキットを30台販売する

  • 結果: 16台を販売
  • 評価: C

目標を上回るペースで順調に進んでいましたが、途中で歯車に異物が挟まるようなトラブルが起き、販売を一時停止せざるを得ませんでした。

3つの新しいマーケティングチャネルを試す

  • 結果: どのチャネルも試せませんでした
  • 評価: F

上記と同じ理由で、再び販売できる商品が用意できるまでマーケティング施策は延期しました。

TinyPilotでマウス操作に対応する

想定よりも難しい作業でしたが、月末ぎりぎりで機能を完成させることができました。マウス対応によって、想像以上にユーザー体験が向上しました。

TinyPilot

指標2020年7月2020年8月変化
ユニーク訪問者数4,9302,284-2,646 (-54%)
総ページビュー数10,4276,136-4,291 (-41%)
販売収益$8,741.37$3,030.74-$5,710.63 (-65%)
寄付N/A$94.06+$94.06 (+inf%)
総収益$8,741.37$3,124.80-$5,616.57 (-64%)

先月にHacker Newsで大きな流入があったときに比べると訪問者数は落ち着いていますが、安定して見込み顧客が訪れてくれているので満足しています。

販売目標に届かなかったのは、在庫を十分に確保できなかったこともありますが、大きな理由は月半ばに販売を停止したことです。

寄付は嬉しい驚きでした。プロジェクトを支援したいという方々から100ドル近くをいただきました。中にはボットと思われるものからの寄付もありました。

ああ、Yケーブルよ、なぜなんだ!

TinyPilotのごく初期の段階から、ずっと悩まされてきた大きな問題があります。それは電源です。

Raspberry Piには、他のUSBデバイスになりすます特別な機能があります。TinyPilotはこの機能を使って対象のコンピュータにキー入力を送っています。コンピュータに対して自分はUSBキーボードだと名乗り、実際のUSBキーボードと同じようにキーストロークを送信する仕組みです。

問題は、キーボードになりすませる唯一のポートが、同時に給電用のメインポートでもあることです。コンピュータのUSBポートからも多少の電力は供給されますが、Piの公式要件である3.0Aには到底足りません。初期バージョンのTinyPilotは0.5Aで動作していましたが、電力不足のまま運用し続けることで予期せぬ不具合が起きるのではないかと常に不安でした。そのため、何とかより良い解決策を見つけたいと考えていました。

そんな中で見つけたのが、このUSB OTG Yケーブルでした。まさに求めていたものに思えました。

Yケーブルの写真

求めていたものに思えたUSB OTG Yケーブル

早速1本購入して試してみると、見事に動作しました。Raspberry Piへの接続を分岐させ、電源と対象のコンピュータの両方に同時につなげることができたのです。そこで供給体制を全面的に切り替え、このケーブルを組み込んだTinyPilot v2を作りました。6台を販売し、新バージョンの宣伝を始めたところ、ある方から「そのケーブルは電源の逆流を防げますか?」という連絡をいただきました。

電源の逆流? そんな問題が起こりうることすら知りませんでした。

調べてみると、このYケーブルはそもそも別々の電源に接続することを想定した作りではないことがわかりました。理論上は、外部電源もコンピュータのUSBポートもともに5Vを出力します。しかし実際には、どちらもぴったり5Vを出力するとは限りません。USBの電源仕様では4.4〜5.25Vの範囲が許容されています。そのため、もしコンピュータ側の出力が4.5Vまで下がった場合、外部電源からコンピュータのUSBポートへ電流が逆流し、ポートに過負荷をかけて最悪の場合は恒久的に破損させてしまう可能性があるのです。

読者の方からこの危険性を指摘された直後、私は販売を停止し、電気設計会社に調査を依頼しました。調査の結果、リスクは現実にあることが確認できたため、すぐに購入者の皆さんに連絡し、解決策が見つかるまで外部電源の接続を外すようお願いしました。幸い、TinyPilotは外部電源がなくても動作します。多少不便にはなりますが。

ゼロから2週間でモノが作れるなんて?

この1か月で最も驚いた発見の一つは、モノづくりがどれほど速く、そして安くなったかということです。

わずか1週間前の8月27日に、TinyPilotの電気設計パートナーに電源問題を解決するためのコネクタの設計を依頼しました。設計は翌日には完成し、すぐに100枚の基板の製造が発注されました。基板は今週末には届く予定です。テストと組み立ても、あと数日で終わる見込みです。

並行して、基板用のケースについては3Dプリントの設計会社と進めています。先方は2日で設計を完成させ、現在は最初の3つの試作品をプリントしているところです。量産が始まれば、1日あたり50個のケースをプリントできる体制です。

TinyPilot用電源コネクタの上面図TinyPilot用電源コネクタの側面図

驚くほどの速さで形になっているTinyPilot用電源コネクタ

すべてが順調に進めば、ケースと基板は早ければ来週にもお客様にお届けできるかもしれません。ゼロの状態から完成したハードウェア製品まで、わずか2週間でたどり着けることになります。

設計費、部品代、人件費をすべて含めても、今回のロットの原価は1台あたり約13ドルになる見込みです。こんなシンプルな製品でも、ここまで短納期・低コストで作れるとは思ってもいませんでした。もちろん、すべてがうまくいけば、という前提ですが。

HIDディスクリプタは悪魔だ

上で述べたように、TinyPilotは対象のコンピュータに対して自身をUSBキーボードとして認識させる必要があります。そのためにUSB接続経由で送っているのが、ヒューマンインターフェースデバイス(HID)ディスクリプタと呼ばれるものです。キーボードやマウス、USBメモリなどのUSBデバイスは、それぞれ何ができるかを宣言するHIDディスクリプタを持っています。

HIDディスクリプタは次のようなバイナリデータの塊です。

// HID descriptor for a keyboard
// Source: https://www.kernel.org/doc/html/latest/usb/gadget_hid.html
static struct hidg_func_descriptor my_hid_data = {
      .subclass               = 0, /* No subclass */
      .protocol               = 1, /* Keyboard */
      .report_length          = 8,
      .report_desc_length     = 63,
      .report_desc            = {
              0x05, 0x01,     /* USAGE_PAGE (Generic Desktop)           */
              0x09, 0x06,     /* USAGE (Keyboard)                       */
              0xa1, 0x01,     /* COLLECTION (Application)               */
              0x05, 0x07,     /*   USAGE_PAGE (Keyboard)                */
              0x19, 0xe0,     /*   USAGE_MINIMUM (Keyboard LeftControl) */
              0x29, 0xe7,     /*   USAGE_MAXIMUM (Keyboard Right GUI)   */
              0x15, 0x00,     /*   LOGICAL_MINIMUM (0)                  */
              0x25, 0x01,     /*   LOGICAL_MAXIMUM (1)                  */
              0x75, 0x01,     /*   REPORT_SIZE (1)                      */
              0x95, 0x08,     /*   REPORT_COUNT (8)                     */
              0x81, 0x02,     /*   INPUT (Data,Var,Abs)                 */
              0x95, 0x01,     /*   REPORT_COUNT (1)                     */
              0x75, 0x08,     /*   REPORT_SIZE (8)                      */
              0x81, 0x03,     /*   INPUT (Cnst,Var,Abs)                 */
              0x95, 0x05,     /*   REPORT_COUNT (5)                     */
              0x75, 0x01,     /*   REPORT_SIZE (1)                      */
              0x05, 0x08,     /*   USAGE_PAGE (LEDs)                    */
              0x19, 0x01,     /*   USAGE_MINIMUM (Num Lock)             */
              0x29, 0x05,     /*   USAGE_MAXIMUM (Kana)                 */
              0x91, 0x02,     /*   OUTPUT (Data,Var,Abs)                */
              0x95, 0x01,     /*   REPORT_COUNT (1)                     */
              0x75, 0x03,     /*   REPORT_SIZE (3)                      */
              0x91, 0x03,     /*   OUTPUT (Cnst,Var,Abs)                */
              0x95, 0x06,     /*   REPORT_COUNT (6)                     */
              0x75, 0x08,     /*   REPORT_SIZE (8)                      */
              0x15, 0x00,     /*   LOGICAL_MINIMUM (0)                  */
              0x25, 0x65,     /*   LOGICAL_MAXIMUM (101)                */
              0x05, 0x07,     /*   USAGE_PAGE (Keyboard)                */
              0x19, 0x00,     /*   USAGE_MINIMUM (Reserved)             */
              0x29, 0x65,     /*   USAGE_MAXIMUM (Keyboard Application) */
              0x81, 0x00,     /*   INPUT (Data,Ary,Abs)                 */
              0xc0            /* END_COLLECTION                         */
      }
};

キーボード用のHIDディスクリプタを作るのは簡単でした。Pythonでキーボードを偽装した事例は多く、ドキュメントも充実していました。

一方、マウスを偽装するのはずっと大変で、HIDディスクリプタの仕組み自体を一から学び直す必要がありました。マウスはボタンの数やスクロールホイールの数、位置情報の方式(絶対座標か相対座標か)など、バリエーションが多いのです。デバッグも厄介で、ディスクリプタは動くか動かないかのどちらかしかありません。無効なディスクリプタを生成しても、何が間違っているのか教えてはくれません。さらに最悪なことに、ディスクリプタを試すたびにRaspberry Piを再起動しなければなりませんでした。

基本的なマウス機能が動くようになるまで、5日間も地道な作業がかかりました。転機となったのは、ツール面に注力したことでした。当初は、次のような巨大で構造のない塊としてディスクリプタを扱っていました。

echo -ne \x05\x01\x09\x02\xA1\x01\x05\x09\x19\x01\x29\x08\x15\x00\x25\x01\x95\x08\x75\x01\x81\x02\x05\x01\x09\x30\x09\x31\x16\x00\x00\x26\xFF\x7F\x75\x10\x95\x02\x81\x02\xC0 > "${MOUSE_FUNCTIONS_DIR}/report_desc"

この状態では、最初からやり直さない限り何も修正できず、考えること自体が困難でした。そこで、さまざまな形式のHIDディスクリプタのサンプルを取り込み、同等のシェルコマンドに変換してファイルを生成できる、簡単なJavaScriptアプリを作りました。

私が作成したHIDフォーマッターツールのスクリーンショット

HIDディスクリプタを整形するために私が作った簡素なJavaScriptアプリ

次に、ホームディレクトリにちょっとしたユーティリティスクリプトを書きました。普段ならわざわざファイルにするほどでもない、ごく単純なものです。

#!/bin/bash

set -x

sudo journalctl -u init-usb-gadget

このささやかなツールが2つの面で役立ちました。1つは、何日も壁にぶつかっている感覚の中で、少しでも前進しているという達成感を得られたことです。HIDディスクリプタはうまくいかなくても、少なくとも意図どおりに動くコードを何か生み出せていました。もう1つは、認知的な負荷を減らし、目の前の問題に集中するための思考の余白を確保できたことです。systemdのログを見るための構文を思い出す代わりに、~/show-systemd-logと打つだけで済むようになりました。

結局、私が抱えていた問題の多くはHIDディスクリプタ自体ではなく、それを生成するために使っていたシェルコマンドにあったことがわかりました。面倒な作業を頭の中から取り除いてみて、初めてディスク上のファイルが期待どおりになっているか確認しようという発想に至りました。実際にはなっていませんでした。そこに気づいてからは、ほどなくして動作するマウスのディスクリプタを完成させることができました。

TinyPilotを使ってリモートのノートパソコンでマウスとキーボードの操作を再現している様子

既存プロジェクトの近況

ここでは、現在も維持はしているものの、開発の主軸ではないプロジェクトについて簡単に近況を報告します。

Is It Keto

指標2020年7月2020年8月変化
ユニーク訪問者数48,23149,981+1,750 (+4%)
総ページビュー数118,980125,599+6,619 (+6%)
ドメインレーティング(Ahrefs)8.09.0+1.0 (+12%)
AdSense収益$208.86$202.46-$6.40 (-3%)
AdThrive収益N/A$35.00N/A
Amazonアフィリエイト収益$134.45$129.88-$4.57 (-3%)
その他アフィリエイト収益$26.60$118.88+$92.28 (+347%)
総収益$369.91$486.22+$116.31 (+31%)

Is It Ketoは今月もわずかに成長しましたが、投資対効果がはるかに高いTinyPilotに注力しようとしています。

今月Is It Ketoで特筆すべき出来事は、ディスプレイ広告をAdSenseからAdThriveに切り替えたことです。残念ながら、移行には想像以上に手間がかかりました。オンボーディングでは、フォームへの入力を求められ、1週間待たされ、また別のフォームへの入力を求められる、といった細かなステップが延々と続きました。

ようやくAdThriveの広告に切り替える段階まで来たところ、今度は彼らのJavaScriptスニペットがシングルページアプリケーションでは正しく動作せず、VueベースのIs It Ketoのコードで不具合を起こすことがわかりました。彼らの顧客の多くはWordPressサイトなのかもしれませんが……2020年にもなってSPAでつまずくとは、正直驚きでした。

そこからはさらに厄介なことになりました。AdThriveは何度も不完全で場当たり的なJavaScriptを送ってきて、それで私のサイトとAdThriveをうまく連携させようとしました。私が実行して「動きません」と報告するたびに、また別の箇所が壊れた新しいスニペットが送られてくる、という繰り返しでした。

最終的には、コードは向こう側でホストしてもらい、私をデバッグのループから外すよう説得しました。十分なテストもせずに本番環境へプッシュしているように感じられ、あまり気分の良いものではありませんが、今はそこに構っている余裕がありません。

Zestful

指標2020年7月2020年8月変化
ユニーク訪問者数440324-116 (-26%)
総ページビュー数1,247841-406 (-33%)
RapidAPI収益$18.05$9.36-$8.69 (-48%)
総収益$18.05$9.36-$8.69 (-48%)

Zestfulは相変わらず静かな状況ですが、新しいAPIマーケットプレイスを検討しています。これまでずっと、現在のプラットフォームであるRapidAPIに代わる選択肢を切望していました。Servernopeという新しい会社が自社のAPIプラットフォームに招待してくれたので、セットアップする時間はないけれど、代わりにZestfulのページを作ってくれるなら歓迎すると伝えたところ、実際に作成してくれました

まだ完全に乗り換える気にはなっていません。RapidAPIで最も不満な点の一つは、アナリティクスがデータを有用な形で提示してくれないことですが、Servernopeも似たような問題を抱えているように見えます。ただ、Servernope経由ではまだ有料ユーザーがいないので、単純に比較するのは難しいところです。

まとめ

今月できたこと

  • TinyPilotの電源問題を調査し、修正用の部品の製造を開始しました。
  • TinyPilotにマウスサポートを追加しました。
  • 在庫管理といくつかのリサーチ業務を引き継ぐフリーランスを採用しました
  • eBayに出品してTinyPilotを海外でも販売できるようにしました。
    • ゆくゆくはすべてShopifyで完結させたいと考えていますが、当面の手軽な解決策としてeBayを活用しています。
  • 新しいブログ記事「How I Collected a Debt from an Unscrupulous Merchant」を公開しました。

学んだこと

  • もっと早い段階で電気設計の専門家に相談する。
    • 振り返ってみると、Raspberry Piの一般的な使い方からかなり外れたことをしていたのですから、計画をプロの電気技術者にレビューしてもらうべきでした。
  • 難しい問題で行き詰まったら、デバッグ作業をなくすためのツールを作る。
    • ツールを作ることで前進している感覚が得られ、思考を解放して問題の本質に集中できるようになります。

来月の目標

  • TinyPilotキットと電源コネクタを60台販売する。
  • 3つの新しいマーケティングチャネルを試す。
  • ITプロフェッショナル7人に、仕事でTinyPilotを使うかどうかインタビューする。

原文は Michael Lynch により に公開されました。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。