TinyPilot: Month 2

Michael Lynch

TinyPilot: 두 번째 달

원문은 Michael Lynch님이 에 게재했습니다. 이 블로그 구독하기

한 줄 요약

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 프린팅 디자인 업체와 협업 중이다. 해당 업체는 이틀 만에 설계를 완료했고, 현재 첫 세 개 프로토타입을 프린팅 중이다. 본격 가동되면 하루에 50개의 케이스를 3D 프린팅할 수 있는 용량을 갖추고 있다.

TinyPilot 블로그 게시물이 1위를 차지한 스크린샷reddit에 올라온 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 디스크립터를 만드는 건 식은 죽 먹기였다. 많은 사람들이 Python으로 가상 키보드를 구현한 적이 있고, 과정도 잘 문서화되어 있었다.

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

기본적인 마우스 기능이 동작하기까지 지루한 작업에 5일이 걸렸다. 나에게 돌파구가 된 건 툴링에 집중한 것이었다. 처음에는 디스크립터를 다음과 같은 거대한 비정형 덩어리로 다루고 있었다:

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

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

HID 디스크립터를 포맷하기 위해 내가 만든 간단한 JavaScript 앱

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

#!/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%)
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가 훨씬 높기 때문에 모든 관심을 TinyPilot에 집중하려 노력 중이다.

Is It Keto와 관련해 주목할 만한 일은 디스플레이 광고를 AdSense에서 AdThrive로 전환한 것이다. 답답하게도 전환 과정이 예상보다 훨씬 많은 관심을 요구했다. 온보딩 과정은 양식을 작성하라고 하고 일주일을 기다리게 한 뒤 또 다른 양식을 작성하라고 하는 등 자잘한 단계가 계속 이어졌다.

마침내 사이트를 AdThrive 광고로 전환하는 단계에 이르렀는데, 그들의 JavaScript 스니펫이 싱글 페이지 앱에서 동작하지 않아 Vue 기반인 Is It Keto 코드에서 오작동했다. 고객 중 많은 수가 WordPress 사이트를 쓰고 있다는 건 이해하지만... 제발! 2020년에 SPA가 그렇게 어려운 변수일 리가 없지 않은가.

그건 완전히 새로운 골칫거리의 시작이었다. AdThrive가 내 사이트와 잘 호환되도록 한다는 엉성하고 망가진 JavaScript를 계속 보내왔기 때문이다. 실행해 보고 안 된다고 보고하면, 또 다른 방식으로 망가진 새로운 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를 통한 유료 사용자가 없어 비교하기 어렵다.

마무리

무엇을 해냈나?

  • TinyPilot의 전원 문제를 조사하고 이를 해결할 부품을 제조하기 시작했다.
  • TinyPilot에 마우스 지원을 추가했다.
  • 재고 관리와 일부 리서치 업무를 맡길 프리랜서를 고용했다.
  • TinyPilot을 해외에 판매하기 위해 eBay 리스팅을 개설했다.
    • Shopify를 통해 모두 처리하는 방법을 모색 중이지만, 당분간 eBay가 쉬운 임시 해결책이다.
  • 새 블로그 글을 게시했다: “How I Collected a Debt from an Unscrupulous Merchant”

배운 점

  • 전기공학 전문가와 더 일찍 협업할 것.
    • 돌아보면, 주류적인 라즈베리 파이 사용법에서 충분히 벗어나 있었으므로 전문가에게 계획을 검토받았어야 했다.
  • 어려운 문제에 막혔을 때는 디버깅 작업을 줄여주는 도구를 만들 것.
    • 도구를 만들면 앞으로 나아가고 있다는 느낌을 주고, 문제의 본질에 집중할 수 있도록 머릿속을 비워준다.

다음 달 목표

  • TinyPilot 키트와 전원 커넥터 60개 판매.
  • 새로운 마케팅 채널 3개 테스트.
  • IT 전문가 7명을 인터뷰하여 업무에서 TinyPilot을 사용할 의향이 있는지 알아보기.

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

댓글