TinyPilot: Month 2

Michael Lynch

TinyPilot:第 2 個月

一句話總結

TinyPilot 給我帶來了一個巨大的難題,但一切仍持續向前推進。

亮點

  • 我暫停了 TinyPilot 的銷售,以處理一個設計問題。
  • 我正在為 TinyPilot 製作一款客製化的 USB 電源連接器。
  • 我所有專案的總營收達到 $3.6k,是有史以來表現最好的一次。

目標達成度評分

每個月月初,我都會設定當月想完成的目標。以下是我的達成狀況:

售出 30 組 TinyPilot 套件

  • 結果:售出 16 組 TinyPilot 套件
  • 評分:C

原本進度超前、有望達標,但後來出了點狀況,我不得不暫停銷售。

測試三個新的行銷管道

  • 結果:未測試任何行銷管道
  • 評分: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 裝置。這就是它能夠向目標電腦輸入按鍵的方式。它會告訴電腦自己是一個 USB 鍵盤,然後以與 USB 鍵盤相同的方式傳送按鍵。

問題在於,唯一能夠模擬鍵盤的連接埠,同時也是接收電力主要來源的連接埠。電腦的 USB 連接埠確實會輸出一些電力,但不足以滿足 Pi 官方要求的 3.0 安培。TinyPilot 的初始版本僅以 0.5 安培運行,雖然可行,但我一直擔心供電不足會導致無法預期的問題,因此急切地想找到更好的解決方案。

最後,我找到了這條 USB OTG Y 型線,看起來正是我需要的:

Y 型線的照片

看似正合我需求的 USB OTG Y 型線

我買了一條,結果真的能用!它將連接到 Raspberry Pi 的線路一分為二,讓我能同時連接電源與目標電腦。我將整個供應鏈轉換為整合這條線材的 TinyPilot v2。在售出六組套件並開始推廣新版本後,有人聯繫我,詢問這條線材是否能防止電力回流。

電力回流?我甚至沒意識到這可能是個問題。

原來,這條 Y 型線並非設計用來連接兩個獨立的電源。理論上,外部電源與電腦的 USB 連接埠都會輸出 5 伏特的電力。但實際上,並無法保證兩者都會精確地輸出 5 V。USB 供電規範允許的範圍在 4.4 - 5.25 V 之間,因此如果電腦端的輸出降至 4.5 V,電流就會從外部電源逆向流回電腦的 USB 連接埠,可能導致連接埠過載甚至永久損壞。

這位讀者一提出這個危險性,我便立即暫停銷售,並聘請了一家電機工程公司進行調查。他們確認風險確實存在,因此我聯繫了顧客,建議他們在找到解決方案前先拔除外部電源。幸好,TinyPilot 在沒有外部電源的情況下仍可運作,只是不那麼方便。

我竟然能在兩週內從零開始製造出東西?

過去一個月中最令我驚訝的發現之一,就是製造的速度與成本已變得如此快速與低廉。

就在一週前,8 月 27 日,我請 TinyPilot 的電機工程合作夥伴設計一個連接器來解決供電問題。設計在隔天就完成了,他們隨即下單印製了 100 片電路板。我們預計這週末就能收到電路板,測試與組裝應該只需要再花幾天時間。

同時,我正與一家 3D 列印設計工作室合作,為電路板製作外殼。這家 3D 列印公司在兩天內完成了設計,目前正在列印前三個原型。一旦進入量產,他們每天有能力 3D 列印 50 個外殼。

TinyPilot 部落格文章位居第一名的螢幕截圖TinyPilot 在 Reddit 上的投稿螢幕截圖

正以驚人速度成形的 TinyPilot 電源連接器

如果一切順利,外殼與電路板最快下週就能交付給顧客。這意味著我們僅用了兩週就從零開始完成了一項實體產品。

包含設計、零件與人工在內,這一批次的總成本預計約為每單位 ~$13。即使是像這樣簡單的專案,我也沒想到週轉時間與成本可以低到這種程度。當然,前提是一切順利。

HID descriptors 是惡魔

如我前面所述,TinyPilot 需要讓自己在目標電腦上顯示為 USB 鍵盤。它透過 USB 連線傳送一種稱為 human interface device (HID) descriptor(HID 描述符) 來達成此目的。像是鍵盤、滑鼠和隨身碟等 USB 裝置都有 HID descriptor,用來宣告裝置的功能。

HID descriptor 是一個二進位區塊,看起來像這樣:

// 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 descriptor 非常輕鬆。許多人已用 Python 實作過虛擬鍵盤,且相關文件相當完整。

實作虛擬滑鼠則困難得多,需要我更深入了解 HID descriptor 的運作方式。滑鼠有更多變異,例如按鍵數量、滾輪數量以及定位類型(絕對定位與相對定位)。除錯非常痛苦,因為描述符只有成功或失敗兩種結果。如果產生了無效的描述符,沒有任何方式可以得知哪裡出錯。最糟的是,每次嘗試一個描述符,都必須重新啟動 Raspberry Pi。

我花了五天繁瑣的工作才讓基本的滑鼠功能運作。對我而言,關鍵在於專注於工具的打造。一開始,我是將描述符當作一大團非結構化的資料塊來處理,像這樣:

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"

這讓我很難思考描述符的結構,因為不從頭來過就無法修改任何東西。因此我寫了一個簡易的 JavaScript 應用程式,讓我能將不同格式的 HID descriptor 範例,轉換為用來產生檔案的等效 shell 指令:

我的 HID 格式化工具螢幕截圖

我為格式化 HID descriptor 而建立的簡易 JavaScript 應用程式

接著,我在家目錄中寫了一些小型的公用程式腳本。這些腳本非常簡單,平常根本不值得獨立成檔:

#!/bin/bash

set -x

sudo journalctl -u init-usb-gadget

這些看似不起眼的工具在兩個方面幫助了我。首先,當我連續好幾天撞牆時,它們給了我一種成就感。即使 HID descriptor 還無法運作,我至少產出了一些能如預期運作的程式碼。再來,它們降低了我的認知負擔,讓我能釋放更多心力專注於眼前的問題。與其回想查看 systemd 日誌的語法,我只需輸入 ~/show-systemd-log 即可。

結果發現,我的大多數問題甚至不是出在 HID descriptor,而是用來建立它們的 shell 指令。一旦我將其他繁瑣的事務從腦中清除,我才意識到應該驗證磁碟上的檔案是否符合我的預期。結果並不符合。一旦發現這點,可用的滑鼠描述符很快就完成了。

使用 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。令人沮喪的是,這個轉換過程比我預期的更耗費心力。導入流程包含了許多瑣碎的步驟,他們會請我填寫某份表單、等待一週、再請我填另一份表單,如此反覆。

終於到了要將網站切換至 AdThrive 廣告的階段,結果發現他們的 JavaScript 程式碼片段在 single-page app(單頁式應用程式) 上無法運作,因此在以 Vue 為基礎的 Is It Keto 程式碼上出現異常。我能理解他們的許多客戶可能都是使用 WordPress 網站,但……拜託!在 2020 年,SPA 不該是這麼大的難題。

這又是一大堆麻煩事,因為 AdThrive 不斷傳給我有問題的、拼湊出來的 JavaScript,號稱能讓 AdThrive 與我的網站相容。我執行後回報說行不通,他們又會傳來另一段以不同方式出錯的 JavaScript 程式碼片段。

最後,我說服他們將程式碼託管在他們那一端,讓我脫離除錯循環。我對於他們似乎在測試不足的情況下就推上正式環境這點不太滿意,但目前我實在沒有餘力去擔心這件事。

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 上還沒有任何付費用戶,所以很難比較。

總結

完成了什麼?

學到的教訓

  • 儘早與電機工程專家合作。
    • 回顧來看,我當時對 Raspberry Pi 的使用方式已偏離主流夠遠,應該要請專業的電機工程師來審視我的計畫。
  • 當你在難題上卡關時,打造能減少除錯工作的工具。
    • 打造工具有助於產生前進的動力,並讓你的心智能專注於問題的核心。

下個月的目標

  • 銷售 60 組 TinyPilot 套件與電源連接器。
  • 測試三個新的行銷管道。
  • 訪談七位 IT 專業人員,了解他們是否會在工作中使用 TinyPilot。

原文由 Michael Lynch 發布

本文章由 muse-spark-1.2-contributor 進行翻譯