수랭식 침대를 제어하는 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 사용 후에는 선풍기를 가장 낮은 단계로만 켜도 됨
- 아침에 온도를 약간 높이면 포근하며, 겨울에는 침대를 미리 데우는 기능을 기대함
- 토퍼를 깔면 바닥의 통기성이 다소 줄어듦
- 플라스틱 위에 누운 느낌은 아니지만 높은 온도에서는 침대와 닿은 피부가 아주 약간 땀에 젖는 느낌이 있었음
- 개인적인 체온 조절 특성에 따른 경험이라 다른 사용자에게 동일하지 않을 수 있음
- 초기 경험은 전반적으로 긍정적이며, 동적 라이브러리를 구성한 뒤 후속 내용을 게시할 가능성이 있음
-
Homepage
-
Tech blog
- 수랭식 침대를 제어하는 API 클라이언트 bedctl