TinyPilot:第 2 個月
一句話總結
TinyPilot 給我帶來了一個巨大的難題,但一切仍持續向前推進。
亮點
- 我暫停了 TinyPilot 的銷售,以處理一個設計問題。
- 我正在為 TinyPilot 製作一款客製化的 USB 電源連接器。
- 我所有專案的總營收達到 $3.6k,是有史以來表現最好的一次。
目標達成度評分
每個月月初,我都會設定當月想完成的目標。以下是我的達成狀況:
售出 30 組 TinyPilot 套件
- 結果:售出 16 組 TinyPilot 套件
- 評分:C
原本進度超前、有望達標,但後來出了點狀況,我不得不暫停銷售。
測試三個新的行銷管道
- 結果:未測試任何行銷管道
- 評分:F
基於上述相同原因,我將行銷工作延後,直到產品重新上架為止。
為 TinyPilot 實作滑鼠整合支援
- 結果:為 TinyPilot 新增了滑鼠支援
- 評分:A
這件事最後比我想像的困難,但我在月底前完成了這項功能。整合滑鼠後,使用者體驗的改善程度超出我的預期。
TinyPilot
| 指標 | 2020 年 7 月 | 2020 年 8 月 | 變化 |
|---|---|---|---|
| 不重複訪客 | 4,930 | 2,284 | -2,646 (-54%) |
| 總瀏覽量 | 10,427 | 6,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 型線,看起來正是我需要的:

看似正合我需求的 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 電源連接器
如果一切順利,外殼與電路板最快下週就能交付給顧客。這意味著我們僅用了兩週就從零開始完成了一項實體產品。
包含設計、零件與人工在內,這一批次的總成本預計約為每單位 ~$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 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,231 | 49,981 | +1,750 (+4%) |
| 總瀏覽量 | 118,980 | 125,599 | +6,619 (+6%) |
| 網域權重 (Ahrefs) | 8.0 | 9.0 | +1.0 (+12%) |
| AdSense 收益 | $208.86 | $202.46 | -$6.40 (-3%) |
| AdThrive 收益 | N/A | $35.00 | N/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 月 | 變化 |
|---|---|---|---|
| 不重複訪客 | 440 | 324 | -116 (-26%) |
| 總瀏覽量 | 1,247 | 841 | -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 的使用方式已偏離主流夠遠,應該要請專業的電機工程師來審視我的計畫。
- 當你在難題上卡關時,打造能減少除錯工作的工具。
- 打造工具有助於產生前進的動力,並讓你的心智能專注於問題的核心。
下個月的目標
- 銷售 60 組 TinyPilot 套件與電源連接器。
- 測試三個新的行銷管道。
- 訪談七位 IT 專業人員,了解他們是否會在工作中使用 TinyPilot。
隨機一篇部落格