수랭식 침대를 제어하는 API 클라이언트 bedctl

1 day ago 4
  • 더운 침실과 체온 민감성 때문에 Sleep.me Chilipad 2.0을 1,700달러에 구입하고, 제조사의 클라우드 API를 제어하는 bedctl을 개발함
  • Chilipad는 물이 순환하는 얇은 토퍼와 펌프·저수조·온도조절기를 결합하며, 요일별 온도 예약과 침대 한쪽부터 설치하는 모듈식 구성을 지원함
  • API는 /devices와 /devices/{:id}에서 기기를 조회하고 PATCH로 온도·작동 상태·화면 밝기·시간대 등을 제어하며, 수위와 현재 수온도 확인할 수 있음
  • Zig으로 작성한 bedctl은 무의존성 라이브러리와 얇은 CLI로 구성되며, 향후 C 인터페이스를 갖춘 libbedctl 동적 라이브러리로 확장할 계획임
  • 초기 수면 추적에서는 회복 수면 시간이 늘고 야간 각성이 줄었지만, 사용 기간이 며칠뿐인 허니문 단계라 장기 효과를 판단하기에는 이름

침대 온도 조절이 필요했던 이유

  • 수면은 면역·인지 기능과 신체 활동 후 회복에 도움이 되지만, 건강 관련 헤드라인에서는 과학적 뉘앙스가 사라질 수 있어 의료진이나 전문가 합의로 뒷받침되는지를 중시함
  • ADHD로 생각이 계속 이어져 잠들기 어려우며, 침대에서 독서하는 습관으로 자극을 줄이고 신경계에 취침 신호를 보내 왔음
  • 자폐 특성으로 특정 질감과 감각에 민감하고 체온 조절도 어려워, 잠들려면 온도와 이불 무게가 적절해야 함
  • Portland의 새 아파트는 에어컨이 거실에만 있고 천장 선풍기도 없어, 고층의 침실이 밤에 가장 더운 공간이 됨
    • 온도 센서를 설치해 침실이 아파트에서 가장 덥다는 점도 확인함
  • 불규칙한 수면, 각성제 복용, 답답한 침실과 체온 민감성이 겹쳐 침대 온도 조절 장치에 비용을 쓰기로 결정함

Chilipad 2.0을 선택한 이유

  • Chilipad 2.0은 물이 든 얇은 매트리스 토퍼를 펌프·저수조·온도조절기가 포함된 장치에 연결해 침대 온도를 제어함
  • 팬으로 공기를 불어 넣는 BedJet은 여름에 차가운 바람이 발에 직접 닿아 제외함
  • Eight Sleep은 가장 저렴한 모델도 3,000달러이고 구독이 필요하며 AI 기능까지 포함돼 구매 대상에서 제외함
  • Chilipad는 침대 한쪽용 장치부터 살 수 있는 모듈식 제품이라 초기 가격이 더 낮음
    • 두 대를 설치하면 양쪽을 서로 다른 온도로 설정할 수 있어 체감 온도가 다른 커플에게 유용함
  • 인터넷 연결 제품이지만 비행기 모드를 지원하고 Bluetooth 리모컨을 기본 제공함
    • 비행기 모드가 Wi-Fi만 끄는지 모든 무선 연결을 차단하는지는 확인하지 않음
    • 처음에는 로컬 API로 잘못 이해했으나 실제로는 클라우드 서비스와 통신함
  • 제조사는 개발자 API, Postman 컬렉션, 온라인 Swagger UI와 OpenAPI 스키마를 제공함

Chilipad 장치 구성

  • Chilipad는 제어 장치인 dock과 물이 순환하는 cover로 구성됨
    • dock에는 온도조절기, 펌프와 저수조가 들어 있음
    • 침대 발치와 방문 가까이에 배치해 열교환기가 내보낸 더운 공기가 침실 안으로 다시 퍼지는 것을 줄임
  • cover는 바닥층, TPU 막으로 만든 hydrolayer, 상단 cover의 세 층으로 구성됨
    • 상단 cover의 지퍼를 열어 사용자가 자는 쪽에 hydrolayer를 설치함
    • 상단에는 만지면 시원한 원단과 얇은 패딩을 사용해 hydrolayer가 직접 느껴지지 않음

Sleep.me API

  • 인증과 Postman 예제

    • API를 사용하려면 먼저 사용자 토큰을 생성해야 하며, 문서는 계정이 있어야 접근할 수 있음
    • Postman 예제는 문서의 세부 정보가 부족할 때 도움이 되지만 별도 회사 계정이 필요하고 구성도 복잡함
    • 오픈소스 hurl 스크립트 같은 대안을 선호함
    • 대부분의 예제에는 변수 이름 오타가 있음
    • {{SleepMeAPI_URL}}이 아니라 대소문자를 구분한 {{SleepMeAPI_Url}}을 사용해야 함
    • “Set Device Status” 예제는 set_temperature_f를 69, thermal_control_status를 standby로 설정함
  • 엔드포인트와 응답 구조

    • API는 기기 목록과 개별 기기를 다루는 두 종류의 엔드포인트를 제공함
    • GET /devices: 토큰에 연결된 기기 목록을 반환함
    • GET /devices/{:id}: 특정 기기의 전체 정보를 반환함
    • PATCH /devices/{:id}: 기기 속성을 변경함
    • /devices의 각 항목에는 기기 ID, 사용자가 정한 이름과 attachments가 포함됨
    • attachments가 연결된 다른 기기를 뜻하는지는 확실하지 않음
    • 상세 응답은 about, control, status 객체로 나뉨
      • about: 펌웨어 버전, 외부 IP, LAN IP, MAC 주소, 모델과 일련번호를 담은 읽기 전용 정보
      • control: 화면 밝기, 표시 온도 단위, 설정 온도, 온도 제어 상태와 시간대 등 변경 가능한 속성
      • status: API 연결 여부, 물 부족 여부, 0~100의 수위와 현재 수온 등 읽기 전용 상태
    • control 속성은 기기별로 다를 수 있으며, 문서에 없는 brightness_level 같은 키가 반환되기도 함
    • thermal_control_status는 active 또는 standby이며 장치가 온도를 능동적으로 조절하는지를 나타냄
  • 온도 값과 장치 제어

    • 화씨 온도는 정수, 섭씨 온도는 실수로 다뤄짐
    • 정상 범위는 화씨 55~115°F, 섭씨 13.0~48.0°C
    • MAX_COLD는 -1 또는 -1.0, MAX_HEAT는 999 또는 999.0으로 표현함
    • 대기 상태에서 status.water_temperature_*가 -1인 것은 센티널 값으로 보이지만 문서화돼 있지 않음
    • 기기 속성을 변경할 때는 바꾸려는 키만 JSON 본문에 넣어 PATCH 요청을 보냄
    • 화씨 온도만 73으로 설정하려면 { "set_temperature_f": 73 }을 전송함
    • 모든 제어 키를 포함하거나 { "control": ... }로 감쌀 필요는 없음
    • 이 요청 방식은 공식 문서가 아니라 Postman 컬렉션을 통해 확인함

bedctl의 구성과 사용법

  • bedctl은 Zig 모듈을 중심으로 만들었으며, CLI는 해당 라이브러리를 호출하는 얇은 래퍼임
  • 이름은 제어 프로그램에 -ctl 접미사를 붙이는 관례를 따르며, “bed cuttle”이 “bed cuddle”처럼 들리는 점도 반영함
  • 주요 명령은 다음과 같음
    • bedctl devices: 토큰에 등록된 기기 이름과 ID 조회
    • bedctl status: 펌웨어, 네트워크 주소, 모델, 설정 온도, 동작 상태, 수위와 관측 온도 조회
    • bedctl set temp 74: 목표 온도 설정
  • 토큰과 기기 ID는 환경 변수로 지정하거나 --token, --device 플래그로 전달할 수 있음

Zig를 선택한 이유와 의존성

  • Rust를 업무에서 약 5년, 취미로 2018년부터 사용했지만 즉각적인 피드백과 낮은 정신적 부담을 원해 최근 선호하는 Zig으로 구현함
  • M1 Max MacBook Pro에서 zig build check --watch -fincremental은 약 75ms에 갱신됨
  • CLI의 유일한 외부 의존성은 TigerBeetle 코드에서 추출·수정하고 Zig 0.16.0에 맞춘 argument parser
  • 라이브러리는 Zig 표준 라이브러리 외에 의존성이 없으며 HTTP 요청에는 std.http.Client를 사용함
  • CLI는 libc와 링크하지 않고, 향후 동적 라이브러리도 libc 없이 구성할 계획임
  • Zig의 기본 교차 컴파일 기능으로 대상 시스템에 직접 접근하지 않고도 지원 플랫폼용 빌드를 만들 수 있음

데이터 모델과 타입 시스템

  • 핵심 구조체는 기기 목록용 Device와 상세 응답용 DeviceStatus임
    • Device는 id, name, attachments를 포함함
    • DeviceStatus는 Device.About, Device.Control, Device.Status로 구성됨
  • Control 속성은 모델마다 없을 수 있어 밝기, 표시 단위, 설정 온도, 작동 상태와 시간대를 선택형 값으로 표현함
  • JSON 문자열을 그대로 쓰는 대신 Zig의 익명 enum을 활용함
    • 온도 단위는 .c와 .f
    • 온도 제어 상태는 .active와 .standby
  • 화씨 설정 온도는 특정 정숫값에 의미 있는 enum 이름을 부여함
    • -1을 기억하는 대신 .max_cold를 사용할 수 있음
    • 일반 온도는 78처럼 정수 리터럴로 전달 가능함
  • 타입만으로 정상 범위인 55~115°F를 제한할 방법은 찾지 못했으며 요청 전 별도 검증도 하지 않음
  • 섭씨 값은 실수여서 허용 범위를 타입으로 표현하지 못함

Zig HTTP 클라이언트 구현 경험

  • Zig 표준 HTTP 클라이언트는 단순한 URI·본문·send 추상화보다 낮은 수준의 절차를 요구함
    • URI 객체 생성
    • 경로를 지정하도록 URI 변경
    • 기본 URI, 헤더와 연결 설정으로 요청 객체 생성
    • 요청 헤드 전송
    • 응답 헤드 수신
    • 본문 reader 생성
    • reader에서 응답 본문 소비
  • 정밀한 제어는 가능하지만, 필요할 때만 저수준으로 내려갈 수 있는 Rust와 달리 Zig에서는 계속 저수준 작업을 하는 느낌이 있음
  • /devices 구현은 인증 헤더를 넣은 GET 요청을 sendBodiless()로 전송한 뒤 응답을 최대 64,000바이트까지 읽어 JSON을 []const Device로 파싱함
  • parseFromSliceLeaky를 사용하지만 외부에 공개하지 않은 arena allocator 구성 덕분에 메모리 해제 문제는 없음

동적 라이브러리 확장 계획

  • 최종 목표는 상위 수준 언어에서 사용할 수 있는 무의존성 동적 라이브러리를 만드는 것임
  • 예정된 구조는 다음과 같음
    • Zig 모듈 bedctl
    • Zig 모듈을 직접 사용하는 CLI bedctl
    • Zig 모듈에 C 인터페이스를 제공하는 libbedctl
    • 동적 라이브러리를 사용하며 C 인터페이스를 자체 검증하는 bedctl-dl
  • Python 래퍼가 만들어지면 Home Assistant에서 사용하고 싶지만 직접 작성할 계획은 없음
  • 동적 라이브러리 빌드와 인터페이스는 아직 준비되지 않았으며 가까운 시점에 추가할 계획임

며칠간 사용한 수면 경험

  • 사용한 며칠 동안 주관적으로 푹 잤고, 피트니스 추적기에서도 회복 수면 단계가 길어지고 야간 각성 횟수가 줄어듦
  • 위스키를 마신 날에도 평소보다 잘 잤지만 이를 술이 수면에 좋다는 뜻으로 해석해서는 안 됨
    • 알코올은 여전히 건강과 수면에 해로우며, 장치가 수면 악화를 일부 줄였을 가능성만 있음
  • 장치를 사용한 지 며칠뿐이고 bedctl도 약 3일 밤에 걸쳐 작성했으므로 결과는 아직 허니문 단계임
  • 처음 온도를 너무 낮게 설정했을 때는 젖은 곳에 누운 듯한 냉감이 있었지만, 온도를 조정한 뒤 사라짐
  • 전환 시간을 직접 측정하지는 않았으나 10분보다는 1분에 가까울 정도로 빠르게 느껴짐
    • 채우는 데 필요한 물이 1갤런 미만이라 가열하거나 냉각할 열용량이 크지 않음

리모컨·선풍기·온열 사용

  • 기본 리모컨은 터치스크린이 달린 큰 다이얼 형태라 예약 온도를 조율하는 동안 즉시 온도를 바꾸기 쉬움
  • 다이얼의 촉감은 만족스럽지 않지만 온도 단위마다 진동해 화면을 보지 않고도 조절할 수 있음
    • 수면 마스크를 쓴 상태에서도 햅틱 피드백으로 변경량을 알 수 있음
  • 약한 공기 흐름도 필요해 이전에는 방을 식힐 풍량과 몸에 직접 닿거나 기도를 건조하게 하지 않을 세기를 동시에 맞춰야 했음
  • Chilipad 사용 후에는 선풍기를 가장 낮은 단계로만 켜도 됨
  • 아침에 온도를 약간 높이면 포근하며, 겨울에는 침대를 미리 데우는 기능을 기대함
  • 토퍼를 깔면 바닥의 통기성이 다소 줄어듦
    • 플라스틱 위에 누운 느낌은 아니지만 높은 온도에서는 침대와 닿은 피부가 아주 약간 땀에 젖는 느낌이 있었음
    • 개인적인 체온 조절 특성에 따른 경험이라 다른 사용자에게 동일하지 않을 수 있음
  • 초기 경험은 전반적으로 긍정적이며, 동적 라이브러리를 구성한 뒤 후속 내용을 게시할 가능성이 있음
Read Entire Article