TinyPilot: Month 2

Michael Lynch

TinyPilot: 2개월 차

한 줄 요약

TinyPilot이 큰 돌발 변수를 던졌지만, 프로젝트는 계속 앞으로 나아가고 있습니다.

하이라이트

  • 설계 문제를 해결하기 위해 TinyPilot 판매를 일시 중단했습니다.
  • TinyPilot용 맞춤형 USB 전원 커넥터를 제작하고 있습니다.
  • 모든 프로젝트를 합친 수익이 총 3,600달러로 역대 최고 수준 중 하나를 기록했습니다.

목표 평가

매달 초에 이달에 이루고 싶은 목표를 정합니다. 목표별 달성 결과는 다음과 같습니다.

TinyPilot 키트 30대 판매

  • 결과: TinyPilot 키트 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 초기부터 저는 하나의 큰 문제로 씨름해 왔습니다. 바로 전원 문제입니다.

라즈베리 파이는 다른 USB 장치를 흉내 낼 수 있는 특별한 능력이 있습니다. 그 기능으로 대상 컴퓨터에 키 입력을 전송합니다. 컴퓨터에 자신이 USB 키보드라고 알린 뒤, 일반 USB 키보드가 키를 전송하는 것과 같은 방식으로 신호를 보내는 것입니다.

문제는 키보드를 흉내 낼 수 있는 유일한 포트가 동시에 전원을 공급받는 메인 포트라는 점입니다. 컴퓨터의 USB 포트는 약간의 전원을 공급하긴 하지만 파이의 공식 요구치인 3.0A에는 턱없이 부족합니다. 초기 버전의 TinyPilot은 0.5A로 동작했고 일단 작동은 했지만, 전력 부족 상태로 계속 구동하면 예상치 못한 문제가 생길까 늘 불안했고, 그래서 더 나은 해결책이 절실했습니다.

그러다 마침내 제게 필요한 것처럼 보이는 USB OTG Y 케이블을 찾았습니다.

Y 케이블 사진

제게 필요한 것 같았던 USB OTG Y 케이블

하나를 사서 써 보니 정말 됐습니다. 라즈베리 파이로 가는 연결을 나눠 전원과 대상 컴퓨터를 동시에 연결할 수 있었습니다. 그래서 전체 공급을 이 케이블을 통합한 TinyPilot v2로 전환했습니다. 키트 6대를 판매하고 새 버전을 홍보하기 시작한 뒤, 누군가가 이 케이블이 역전류를 막아 주는지 물어왔습니다.

역전류요? 그런 문제가 생길 수 있다는 것조차 모르고 있었습니다.

알고 보니 Y 케이블은 서로 다른 전원을 연결하도록 만들어진 제품이 아니었습니다. 이론상으로는 외부 전원과 컴퓨터 USB 포트 모두 5V를 출력합니다. 하지만 실제로 두 쪽이 정확히 5V를 출력한다는 보장은 없습니다. USB 전원 규격은 4.4~5.25V 범위를 허용하므로, 컴퓨터 출력이 4.5V로 떨어지면 외부 전원 장치의 전류가 컴퓨터 USB 포트로 역류해 포트를 과부하시키고 영구적으로 손상시킬 수 있습니다.

독자가 이 위험을 알려주자마자 판매를 중단하고 전기 엔지니어링 업체에 조사를 의뢰했습니다. 실제로 위험이 존재한다는 확인을 받아 고객들에게 연락해 해결책을 찾을 때까지 외부 전원 연결을 해제하도록 안내했습니다. 다행히 TinyPilot은 외부 전원 없이도 동작합니다. 다만 조금 불편할 뿐입니다.

맨바닥에서 2주 만에 제품을 만들 수 있다고?

지난 한 달간 가장 놀라웠던 발견 중 하나는 제조가 얼마나 빠르고 저렴해졌는지였습니다.

불과 일주일 전인 8월 27일에 TinyPilot의 전기 엔지니어링 파트너에게 전원 문제를 해결할 커넥터 설계를 요청했습니다. 설계는 다음 날 바로 나왔고, 즉시 100장의 기판에 인쇄를 주문했습니다. 이번 주말이면 기판이 도착할 예정이며, 테스트와 조립에는 며칠 더 걸릴 것으로 예상됩니다.

동시에 회로 기판을 담을 케이스 제작을 위해 3D 프린팅 디자인 업체와 협업하고 있습니다. 3D 프린팅 업체는 이틀 만에 설계를 마쳤고, 현재 첫 세 개 프로토타입을 출력 중입니다. 본격적으로 가동되면 하루에 50개의 케이스를 3D 프린팅할 수 있습니다.

TinyPilot 전원 커넥터 위에서 본 모습TinyPilot 전원 커넥터 옆에서 본 모습

놀라울 정도로 빠르게 완성되고 있는 TinyPilot 전원 커넥터

모든 것이 순조롭게 진행되면 빠르면 다음 주에 케이스와 기판을 고객에게 제공할 수 있습니다. 그렇다면 무에서 완성된 실물 제품까지 단 2주 만에 도달하는 셈입니다.

설계, 부품, 인건비를 모두 합쳐 이번 생산분의 단가는 개당 약 13달러 선이 예상됩니다. 이렇게 단순한 프로젝트라도 이렇게 짧은 납기와 낮은 비용이 가능할 줄은 몰랐습니다. 물론 모든 것이 잘 풀린다는 전제 하에서 말입니다.

HID 디스크립터는 악마다

위에서 설명한 대로 TinyPilot은 대상 컴퓨터에 자신을 USB 키보드로 인식시켜야 합니다. 이를 위해 USB 연결을 통해 HID(Human Interface Device) 디스크립터라는 것을 전송합니다. 키보드, 마우스, 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 디스크립터를 만드는 것은 아주 쉬웠습니다. 이미 많은 사람이 파이썬으로 가짜 키보드를 구현해 놓았고, 과정도 잘 문서화되어 있었습니다.

가짜 마우스를 구현하는 것은 훨씬 어려웠고, HID 디스크립터의 동작 원리를 더 깊이 공부해야 했습니다. 마우스는 버튼 수, 스크롤 휠 수, 포지셔닝 방식(절대 좌표 vs 상대 좌표) 등 변수가 훨씬 많습니다. 디버깅도 고통스러웠는데, 디스크립터는 되거나 안 되거나 둘 중 하나이기 때문입니다. 잘못된 디스크립터를 만들면 무엇이 잘못됐는지에 대한 피드백을 받을 방법이 없습니다. 최악인 점은 디스크립터를 테스트할 때마다 라즈베리 파이를 재부팅해야 한다는 것이었습니다.

기본적인 마우스 기능이 동작하기까지 지루한 작업이 닷새나 걸렸습니다. 저에게 전환점이 된 것은 툴링에 집중한 것이었습니다. 처음에는 다음과 같이 거대하고 구조화되지 않은 덩어리로 디스크립터를 다루고 있었습니다.

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 디스크립터 예제를 가져와 파일을 생성하는 동등한 셸 명령어로 변환해 주는 간단한 자바스크립트 앱을 만들었습니다.

내가 만든 HID 포매터 도구 스크린샷

HID 디스크립터를 정리하기 위해 제가 만든 간단한 자바스크립트 앱

다음으로 홈 디렉터리에 작은 유틸리티 스크립트들을 만들었습니다. 보통은 파일로 따로 만들 가치도 없을 정도로 단순한 스크립트들이었습니다.

#!/bin/bash

set -x

sudo journalctl -u init-usb-gadget

이런 단순한 도구들이 두 가지 면에서 도움이 됐습니다. 첫째, 며칠 동안 벽에 머리를 박는 기분이 들 때 성취감을 줬습니다. HID 디스크립터는 동작하지 않았지만, 적어도 원하는 대로 동작하는 무언가의 코드를 만들고 있다는 느낌이 들었습니다. 둘째, 인지 부하를 줄여 당면한 문제에 더 집중할 수 있게 해 줬습니다. 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%)
애드센스 수익$208.86$202.46-$6.40 (-3%)
AdThrive 수익N/A$35.00N/A
아마존 제휴 수익$134.45$129.88-$4.57 (-3%)
기타 제휴 수익$26.60$118.88+$92.28 (+347%)
총 수익$369.91$486.22+$116.31 (+31%)

Is It Keto는 이번 달에 소폭 성장했습니다. ROI가 훨씬 높은 TinyPilot에 모든 관심을 집중하려 하고 있긴 하지만 말입니다.

이번 달 Is It Keto에서 눈에 띄었던 일은 디스플레이 광고를 애드센스에서 AdThrive로 전환한 것입니다. 짜증나게도 전환 과정이 예상보다 훨씬 많은 관심을 요구했습니다. 온보딩 과정은 양식을 하나 작성하라고 하고 일주일을 기다리게 한 뒤 또 다른 양식을 작성하라고 하는 식의 자잘한 단계들이 끝없이 이어졌습니다.

마침내 사이트를 AdThrive 광고로 전환하는 단계에 이르렀는데, 그들의 자바스크립트 스니펫이 싱글 페이지 앱에서는 동작하지 않아 Vue 기반인 Is It Keto 코드에서 오작동했습니다. 많은 고객이 워드프레스 사이트를 사용한다는 점은 이해하지만, 그래도… 2020년에 SPA가 이렇게 큰 변수일 일은 아니지 않나요!

그때부터는 완전히 새로운 골칫거리의 연속이었습니다. 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”

배운 점

  • 전기 엔지니어링 전문가와 더 일찍 협업할 것.
    • 돌아보니 라즈베리 파이의 일반적인 사용법에서 꽤 벗어나 있었기 때문에, 전문가에게 계획을 검토받았어야 했습니다.
  • 어려운 문제에 막혔을 때는 디버깅 작업을 줄여 주는 도구를 만들 것.
    • 도구를 만들면 앞으로 나아가고 있다는 감각이 생기고, 문제의 본질에 집중할 수 있도록 머리를 비울 수 있습니다.

다음 달 목표

  • TinyPilot 키트와 전원 커넥터 60개 판매
  • 새로운 마케팅 채널 3개 테스트
  • IT 전문가 7명에게 업무에서 TinyPilot을 사용할지 인터뷰

원문은 Michael Lynch님이 에 게재했습니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.