TinyPilot: Month 2

Michael Lynch

TinyPilot:第 2 个月

一句话总结

TinyPilot 给我出了一个大难题,但事情仍在继续向前推进。

亮点

  • 我暂停了 TinyPilot 的销售,以解决一个设计问题。
  • 我正在为 TinyPilot 制造定制 USB 电源连接器。
  • 我所有项目的总收入达到 3.6 千美元,是有史以来表现最好的月份之一。

目标评分

每个月开始时,我都会列出自己希望完成的事情。下面是我对照这些目标的完成情况:

销售 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 descriptor(人机接口设备描述符)真是个恶魔

正如我上面所述,TinyPilot 需要向目标计算机呈现为一个 USB 键盘。它通过 USB 连接发送所谓的 HID descriptor。键盘、鼠标和 U 盘等 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 的工作方式。鼠标有更多种变化,例如按键数量、滚轮数量,以及定位方式(绝对定位还是相对定位)。调试非常痛苦,因为 descriptor 要么能用,要么不能用。如果生成了无效的 descriptor,就完全无法获得任何反馈来了解它哪里出了问题。最糟糕的是,每次尝试一个 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"

这让人很难理解 descriptor,因为不从头开始就无法修改任何内容。我写了一个简易的 JavaScript 应用,让我可以输入不同格式的示例 HID descriptor,然后将其转换为等效的 shell 命令,用来生成文件:

我的 HID 格式化工具截图

我制作的一个简陋 JavaScript 应用,用来为我格式化 HID descriptor

接下来,我在自己的主目录中写了一些小工具脚本。它们都是非常简单的脚本,通常根本不值得单独存成文件:

#!/bin/bash

set -x

sudo journalctl -u init-usb-gadget

这些不起眼的工具在两个方面帮了我。首先,当我连续几天都像在撞墙一样毫无进展时,它们让我有一种有所成就的感觉。虽然 HID descriptor 还不能工作,但至少我还在产出一些能够按预期运行的代码。其次,它们降低了我的认知负担,让我有更多脑力专注于眼前的问题。我不用再回忆查看 systemd 日志的语法,只需输入 ~/show-systemd-log 即可。

后来我发现,我遇到的大多数问题甚至都不是 HID descriptor,而是我用来创建它们的 shell 命令。一旦把其他烦琐的任务从脑中清除出去,我就意识到应该检查磁盘上的文件是否符合预期。结果并不符合。意识到这一点后,我很快就做出了一个能工作的鼠标 descriptor。

使用 TinyPilot 在远程笔记本电脑上模拟鼠标和键盘操作

旧项目

下面是一些我仍在维护、但不是当前开发重点的项目简要进展:

Is It Keto

指标2020 年 7 月2020 年 8 月变化
独立访客48,23149,981+1,750 (+4%)
总页面浏览量118,980125,599+6,619 (+6%)
Domain Rating(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 上,因为那里的 ROI 更高。

Is It Keto 唯一值得一提的事情,是我把展示广告从 AdSense 换成了 AdThrive。令人沮丧的是,这次转换耗费的精力远远超出了我的预期。接入过程包含许多琐碎步骤:他们让我填写一份表格,等一周,再让我填写另一份表格,如此循环往复。

最后,我们到了把网站切换到 AdThrive 广告的阶段,结果发现他们的 JavaScript 代码片段无法在 single-page app(SPA,单页应用)上运行,因此在基于 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 发布

本文章由 openai/gpt-5.6-luna 进行翻译