TinyPilot: Month 2

Michael Lynch

TinyPilot:第二个月

原文由 Michael Lynch 发布,订阅该博客

一句话总结

TinyPilot 给我来了个措手不及,不过整体仍在向前推进。

亮点

  • 为解决一个设计问题,我暂停了 TinyPilot 的销售。
  • 我正在为 TinyPilot 定制生产一款 USB 供电连接器。
  • 所有项目的总收入达到了 3600 美元,是迄今表现最好的月份之一。

目标完成情况

每个月初,我都会定下当月想要完成的目标。以下是完成情况:

售出 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 最早期开始,我就一直在为一个大问题头疼:供电。

树莓派有一个特殊能力,可以模拟其他 USB 设备。正是靠这个,它才能向目标电脑输入按键。它会告诉电脑自己是一块 USB 键盘,然后像真正的 USB 键盘那样发送按键。

问题在于,唯一能模拟键盘的那个接口,同时也是主要的供电口。电脑的 USB 口确实会输出一点电量,但不足以满足树莓派官方要求的 3.0 安培。最初版本的 TinyPilot 只靠 0.5 安培运行,虽然能用,但我一直担心供电不足会引发各种意外问题,所以急于寻找更好的解决方案。

最后,我找到了这根 USB OTG Y 型线,看起来正是我需要的:

Y 型线的照片

这根 USB OTG Y 型线看起来正合我意

我买了一根,一试居然成功了!它把树莓派的连接一分为二,让我可以同时连接电源和目标电脑。于是我把整个供应都切换到了集成这根线的 TinyPilot v2。在卖出六套并开始宣传新版本后,有人联系我,问这根线是否能防止电流倒灌。

电流倒灌?我压根没意识到这会是个问题。

原来,这种 Y 型线本来就不是用来连接两个独立电源的。理论上,外部电源和电脑的 USB 口都输出 5 伏电压。但实际上,并不能保证两者都恰好是 5 伏。USB 供电规范允许的范围是 4.4 至 5.25 伏,所以如果电脑端的电压掉到 4.5 伏,电流就会从外部电源反向流入电脑的 USB 口,可能会导致接口过载甚至永久损坏。

读者一指出这个隐患,我立刻暂停销售,并聘请了一家电气工程公司来调查。他们确认风险确实存在,于是我马上联系了已购买的客户,建议他们在找到解决方案前先断开外部供电。好在 TinyPilot 即使不接外部电源也能工作,只是不太方便。

两周内从零造出一个硬件?

过去一个月里最让我惊讶的发现之一,就是如今制造的速度和成本之低。

就在一周前的 8 月 27 日,我请 TinyPilot 的电气工程合作方设计一款连接器来解决供电问题。设计第二天就完成了,他们随即下单印制了 100 块电路板。预计这周末就能到货,测试和组装也只需要再花几天。

与此同时,我还在和一家 3D 打印设计工作室合作,为电路板设计外壳。对方两天内就完成了设计,目前正在打印前三个原型。一旦开工,他们每天能打印 50 个外壳。

TinyPilot 博客文章排名第一的截图Reddit 上 TinyPilot 投稿的截图

正在以惊人速度成型的 TinyPilot 电源连接器

如果一切顺利,外壳和电路板最快下周就能交付给客户。这意味着我们仅用两周就从零做出了一款完整的实体产品。

算上设计、零件和人工,这一批的总成本大约是每件 13 美元。即使是这么简单的项目,我也没想到周期和成本能低到这种程度——当然,前提是一切顺利。

HID 描述符真要命

上文所述,TinyPilot 需要向目标电脑表明自己是一块 USB 键盘。它通过 USB 连接发送一种叫做人机接口设备(HID)描述符的东西来实现。键盘、鼠标、U 盘等 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 描述符的原理。鼠标的变体很多,比如按键数量、滚轮数量、定位方式(绝对定位还是相对定位)等。调试非常痛苦,因为描述符要么能用,要么不能用。如果生成了一个无效的描述符,根本得不到任何关于哪里出错的反馈。更糟的是,每尝试一次描述符,都得重启一次树莓派。

我花了五天枯燥的摸索,才让基本的鼠标功能跑起来。对我来说,关键在于先把工具做好。一开始,我把描述符当成一大坨无结构的二进制来处理,像这样:

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 描述符示例转换成对应的 shell 命令来生成文件:

我的 HID 格式化工具截图

我写的一个用于格式化 HID 描述符的简易 JavaScript 工具

接着,我在主目录下写了一些小小的实用脚本。这些脚本简单到平时根本不值得单独成文件:

#!/bin/bash

set -x

sudo journalctl -u init-usb-gadget

这些看似笨拙的小工具在两方面帮了我。第一,当我连续几天撞墙般毫无进展时,它们至少让我有了点成就感——就算 HID 描述符还没跑通,我至少写出了能按预期工作的代码。第二,它们降低了我的认知负担,让我能把更多精力集中在核心问题上。不用再去回忆查看 systemd 日志的语法,直接输入 ~/show-systemd-log 就行。

后来发现,我的大部分问题根本不在 HID 描述符本身,而在于用来生成它们的 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 代码在单页应用上无法正常工作,导致基于 Vue 的 Is It Keto 代码出现了异常。我理解他们很多客户可能用的是 WordPress,但……拜托!在 2020 年,单页应用不该是什么难题吧。

这下又捅了个新马蜂窝,因为 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 是一个便捷的过渡方案。
  • 发布了一篇新博文:《我是如何向无良商家追回欠款的》

经验教训

  • 更早地与电气工程专家合作。
    • 回过头看,我对树莓派的用法已经偏离主流足够远,本就应该请专业电气工程师来审核方案。
  • 遇到难题卡住时,先做一些能减少调试工作的工具。
    • 做工具能让你感到自己在前进,并让大脑腾出空间专注于问题的本质。

下月目标

  • 售出 60 套 TinyPilot 套件及电源连接器。
  • 测试三个新的营销渠道。
  • 访谈七位 IT 从业者,了解他们是否会在工作中使用 TinyPilot。

本文章由 muse-spark-1.2-contributor 进行翻译

评论