<div align="center">
FrankenSonos
<img src="https://raw.githubusercontent.com/Dicklesworthstone/frankensonos/HEAD/frankensonos_illustration.webp" alt="FrankenSonos illustration" width="900">
당신만의 Sonos, 당신만의 방식. Tailscale tailnet에 연결되어 있는 한 전 세계 어디서든 스피커를 제어할 수 있습니다. 이미 소유하고 있는 Sonos 스피커들을 위한 메모리 안전한 Rust 컨트롤러 및 데몬입니다: 터미널, HTTP API 또는 MCP를 사용하는 모든 AI 에이전트에서 집 전체를 구동하는 단일 바이너리를 제공합니다.
status: pre-release
language: Rust 2024
license: MIT + rider
interfaces: CLI · HTTP · MCP
networks: S1 + S2
</div">
상태: 프리-릴리스 (0.1.0 이전). 아래에 있는 모든 기능은 구현되었으며, 모든 기능은 가상의 Sonos 하우스인 fsonos-sim을 통해 종단 간(end to end)으로 테스트되었습니다 (S1 및 S2가 루프백에서 SOAP 및 GENA를 사용하는 가정이며, 실제 플레이어의 스크럽된 캡처본과 비교 검증됨). 이 데몬은 또한 실제 두 개의 하우스(S1 및 S2)를 구동하며, 여기서는 디스커버리, 그룹화, 볼륨, 즐겨찾기, Spotify 재생, 공지 기능이 실제 플레이어에서 실행되었으며, fsonos selftest는 아무것도 들리지 않게 당신의 시스템을 점검합니다. 아직 실제 하드웨어 CI는 없으며, 인터페이스는 0.1.0 이전까지 변경될 수 있습니다.
FrankenSonos를 사용하는 이유 (Why FrankenSonos)
Sonos 하드웨어는 훌륭하고 몇 년 동안 지속되지만, 주변 소프트웨어에서 부족함을 보입니다. 앱은 스피커를 분실하게 하고, 그룹화 기능은 사용자를 괴롭히며, 자동화는 불가능에 가깝고, 레거시 S1 라인은 동결된 반면 S2는 이를 무시하고 발전하며, Spotify Web API로는 Sonos에서 재생을 시작할 수 없습니다.
FrankenSonos는 스피커들이 이미 사용하는 로컬 프로토콜(SSDP 디스커버리, 포트 1400의 UPnP/SOAP, GENA 이벤트)을 통해 스피커에 직접 연결하며, 집 전체를 두 세대 모두 한 번에 당신과 에이전트가 구동할 수 있는 무언가로 만듭니다.
- tailnet의 어디서든 사용 가능.
fsonos serve는 루프백 및 Tailscale 주소(루프백에서 MCP)에 HTTP API를 제공하며, fsonos tailscale setup은 Tailscale Serve를 사용하여 이 둘 앞에 HTTPS를 배치합니다. 다른 도시에 있는 휴대폰이든, 클라우드 VM의 에이전트든, 직장에서의 노트북이든: 만약 그것이 사용자의 tailnet에 연결되어 있다면, 그곳에서 집을 운영할 수 있습니다 (CLI도 마찬가지입니다: FSONOS_DAEMON_URL=https://<mac>.<tailnet>.ts.net fsonos pause Kitchen). 아무것도 공용 인터넷에 노출되지 않으며, Funnel은 절대 사용되지 않고, 스피커는 결코 사용자의 LAN을 벗어나지 않습니다.
- 가드레일을 갖춘 에이전트용 설계. 모든 인터페이스(CLI, HTTP, MCP)는 하나의 제어 경로를 거칩니다: 집 정책(방별 볼륨 제한, 조용한 시간, 클라이언트별 도구 허용 목록), 누가 무엇을 했는지에 대한 액션 로그, 그리고 되돌리기 기능입니다. 볼륨 제한과 조용한 시간은 에이전트 인터페이스를 구속하며; 로컬 CLI는 신뢰할 수 있고 기본적으로 무제한으로 실행됩니다 (CLI도 제한하려면
[clients.cli] capped = true를 설정하세요). 이러한 제한은 에이전트가 시작하는 음악뿐만 아니라 설정하는 볼륨에도 적용됩니다: 재생, 재개 또는 DJ 시작을 하면 방의 볼륨이 즉시 최대치로 낮아집니다. 밤 2시에 볼륨 80으로 음악을 트는 것을 막고 집을 에이전트에게 맡기세요.
- 사용자가 원하는 것을 재생하는 DJ. 사용자의 Spotify 좋아요 및 저장 목록에서, 어떤 장르를 아우르는지(팝, 재즈, 힙합, 사운드트랙, 클래식) 상관없이 음악을 재생하며, 추론하는 것보다 사용자의 의도를 우선시하고, 분위기와 시간에 따라 흐름을 따르며, 아티스트들로 세트를 구성하고, 선택한 모든 곡에 대한 설명을 제공하며, 좋아요, 싫어요, 초반 건너뛰기, 전체 청취를 통해 계속 학습합니다. 클래식 작품은 통째로, 각 악장 순서대로 재생됩니다. 사용자의 선호도(좋아하거나 피하는 장르, 아티스트 및 시대, 기본 에너지 레벨, 명시적 트랙, 핀 및 금지 목록)는 CLI, HTTP, MCP에서 추론되는 내용을 무효화합니다.
- 단일 메모리 안전 바이너리. Pure Rust 2024를 사용하며
#![forbid(unsafe_code)] 지침을 따르고, 소유권 기반의 비동기 스택(Tokio나 reqwest 미사용) 위에서 작동합니다. 스피커에는 아무것도 설치되지 않으며, 그 어떤 것도 변경되지 않습니다.
기능
| 영역 | 얻을 수 있는 것 |
|---|
| 발견 및 토폴로지 | S1과 S2 가구의 모든 플레이어, 해당 구역 그룹 및 코디네이터. 방은 이름으로 해결됩니다 (Room@S1 / Room@S2), 사용자 지정 별칭 (fsonos rooms alias add downstairs Kitchen "Living Room"), here (기본 방, aliases.toml의 [defaults] room), 또는 all / everywhere). |
| ... | |
| DJ | dj start (선택적으로 --mood), skip, stop; Spotify 좋아요 트랙 및 저장된 앨범에서 모든 장르를 골라 재생합니다. 노래가 자체적으로 재생되거나, 클래식 작품이 전체 악장 순서대로 연주됩니다. 아티스트(또는 작곡가), 시대, 시간대별 에너지에 따라 다양하게 설정할 수 있습니다. 분위기, 아티스트, 장르, 10년 단위, 키워드(장르와도 일치함), 작품 길이 또는 에너지를 통해 dj steer 할 수 있으며, 클래식 음악의 경우 작곡가 및 시대도 가능합니다. (제외 항목 지정 가능: --not-decade 80s, --not-genre pop). 일정 시간 동안 또는 지우기 전까지 유효합니다. dj prefs는 선호하는 기본 설정, 피해야 할 것, 기본 에너지 레벨을 설정하고 고정하거나 금지할 수 있습니다. dj status와 dj why는 선택 요인을 항목별로 설명해 줍니다. dj rehearse는 아무것도 재생하지 않고도 특정 시간대에 재생될 세트를 계획하며, 각 선택의 이유와 각 시간대의 다양성을 보여줍니다. dj moods 및 사용자 지정 moods.toml을 프로그래밍할 수 있습니다. dj like / dislike, 초반 건너뛰기(skip)와 전체 청취가 나중 선택에 영향을 줍니다. |
| 장면 (Scenes) | scene save dinner는 그룹화, 볼륨, 음소거 및 각 그룹이 재생하는 것을 포착합니다. 또는 --from dinner.toml을 사용하여 저장할 수 있습니다 (즐겨찾기, 스트림, Spotify 링크 또는 그룹별 분위기를 가진 DJ). scene apply dinner는 해당 가구가 필요한 단계만 전송하며, --fade 20s로 볼륨이 점진적으로 올라갑니다. 그리고 fsonos undo가 이를 되돌립니다. |
| 수면 및 스케줄 | sleep Bedroom 45m는 그룹을 마지막 2분 동안 서서히 종료합니다(발표자 자신의 타이머가 백업 역할을 함). schedule add "weekdays 07:30" dj start Kitchen --mood bright (무드는 시간대 프로그램이 변경될 때까지 지속되며, 이후에는 해당 프로그램이 제어권을 가짐), 또는 일시 정지, 볼륨 조절, 장면 전환(정책의 페이드에 걸쳐 점진적으로 변화)을 특정 시간에 하거나 지연 후에 실행합니다. 추가한 사용자의 권한으로 실행됩니다. 재시작된 데몬은 수면 타이머를 다시 가져옵니다.|
| ... |
빠른 둘러보기 (Quick tour)
fsonos discover
fsonos zones
fsonos play "Living Room" --favorite "Morning"
...
스피커가 준비되어 있지 않나요? 터미널 하나에서 fsonos sim을 실행하세요. 이 명령어는 다른 모든 명령(및 fsonos mcp)이 가상 하우스를 바라보도록 하는 --seeds 및 --routes 플래그를 출력합니다.
라이브 컴퓨터 오디오 (실험적)
스피커가 연결된 LAN에 있는 컴퓨터에 libmp3lame 인코더와 PipeWire/PulseAudio 호환 오디오 서버를 사용하여 FFmpeg을 설치해야 합니다. pactl list short sources를 사용하여 출력 장치의 모니터(monitor) 소스를 찾은 다음, 다음 명령어를 사용합니다:
fsonos stream Kitchen --input <출력-소스>.monitor
...
구성된 루프백/가상 오디오 장치가 있는 macOS의 경우, --format avfoundation --input :<장치-인덱스>를 선택합니다( ffmpeg -f avfoundation -list_devices true -i ""로 확인). 인코더는 로컬에서 시작되며; Sonos 플레이어는 해당 플레이어에 도달하는 데 사용된 LAN 인터페이스에만 바인딩된 무작위 이름의 읽기 전용 엔드포인트에서 MP3를 가져옵니다. 공개 오디오 서비스나 클라우드 릴레이가 없습니다. 라이브 캡처에는 --daemon-url이 지원되지 않습니다: stream을 실행하는 컴퓨터는 플레이어에 도달할 수 있어야 하며, 플레이어로부터 접근 가능해야 합니다.
비디오 립싱크는 실험적입니다: Sonos는 HTTP 라디오 재생을 초 단위로 버퍼링하며, 이 지연 시간이 표류합니다. 일반적인 stream은 청취용이며, 동기화된 비디오 오디오를 위한 것이 아닙니다. --latency-wav는 시간 정렬된 참조/마이크 녹음으로부터의 지연 시간을 측정하며, --sync-mpv <소켓> --sync-input <소스>는 스트리밍하는 동안 mpv의 audio-delay가 실시간 페어링된 참조/마이크 소스로부터 보정되도록 유지합니다. 둘 다 Acoustic calibration에 설명된 캡처 설정이 필요하며, 실제 스피커에서는 아직 검증되지 않았습니다. 시작 시의 보정음과 자동 소스 페어링은 여전히 issue #2에서 추적되고 있습니다.
오프라인 음성 및 WAV 공지
Linux는 먼저 FrankenTTS (ftts)를 발견하고, 그 다음 espeak-ng를 사용합니다. 공지하기 전에 엔진과 로컬 모델을 설치해야 합니다. FrankenTTS의 ftts pull이 모델을 준비합니다. 명시적인 Piper 모델이 자동 Linux 선택에서 우선권을 갖습니다: piper_model 또는 FSONOS_PIPER_MODEL을 로컬 .onnx 파일과 그 옆에 있는 .onnx.json 사이드카로 설정하거나, 해당 모델을 --voice로 전달합니다. macOS는 기본적으로 /usr/bin/say를 계속 사용합니다. `fsonos say
어댑터는 stdin으로 텍스트를 전송하고 개별 인자 내에서 정확히 하나의 {output}과 선택적 {voice}만 치환하며, 셸 보간(shell interpolation)은 수행하지 않습니다.
FSONOS_TTS_BACKEND, FSONOS_TTS_VOICE, FSONOS_PIPER_MODEL, 그리고 FSONOS_TTS_TIMEOUT_SECS는 파일 설정을 재정의합니다. FSONOS_TTS_COMMAND는 JSON 인자 배열(위의 command와 동일한 형태)이며, 다른 백엔드가 구성되었을 때도 명령 어댑터를 선택합니다. 요청의 --voice가 구성된 음성을 재정의합니다. 합성(Synthesis)은 기본적으로 180초로 설정되며, 허용되는 timeout_secs 값은 1–600입니다.
announce --file은 클라이언트의 WAV 바이트를 데몬에 업로드합니다. POST /announce와 데몬의 MCP announce 도구는 text, chime, 또는 wav_base64(표준 패딩된 base64) 중 정확히 하나, 그리고 rooms와 volume을 받습니다. 업로드된 WAV는 16비트 PCM이어야 하며, 최대 16 MiB 및 5분까지 가능합니다. 모든 클립은 동일한 검증된 미디어 리스너, 볼륨 제한 및 복원 기능을 사용합니다. MCP를 통한 공지(Announcement) 재생에는 fsonos serve가 필요하며, 독립형 fsonos mcp는 미디어 리스너가 없습니다.
tailnet의 어디에서든 접근하기
데몬은 LAN을 벗어나 오직 사용자의 tailnet으로만 나갑니다:
fsonos serve
fsonos tailscale setup
fsonos doctor --only tailscale
fsonos tailscale setup은 443 (API) 및 8443 (MCP)의 HTTPS를 데몬의 루프백 포트로 매핑하고, 두 포트 모두 Funnel이 활성화된 동안에는 실행을 거부하며, 자신이 생성하지 않은 Serve 설정(status, teardown, --dry-run)은 절대 건드리지 않습니다.
- 데몬을
--tailscale-serve (또는 FSONOS_TAILSCALE_SERVE=1) 플래그와 함께 Serve 뒤에서 실행합니다: 이 방식은 시작 시 Serve를 설정하고 로그인 Serve가 전달하는 것을 신뢰하므로, 각 HTTP 및 MCP 호출자는 자신의 Tailscale 로그인 이름으로 식별됩니다. policy.toml의 [clients."[email protected]"] 테이블은 해당 사용자의 장치가 수행할 수 있는 작업을 정의하며, 액션 로그는 누가 무엇을 했는지 기록합니다. 이 플래그가 없으면, Serve를 통해 도착하는 HTTP 및 MCP 호출자는 데몬 자체의 루프백 권한(loopback-http)을 얻게 되며, fsonos doctor --only tailscale이 이에 대해 경고합니다. 직접적인 tailnet 리스너는 Tailscale WhoIs를 사용하여 실제 소켓 피어를 식별합니다. 각 MCP 요청, 세션 및 리소스 구독은 허용된 호출자를 유지하므로, 동시 에이전트는 자체 정책, 액션 기록 및 소유권을 가집니다. 여러 MCP 리스너가 동일한 라이브 하우스를 공유합니다. 네이티브 MCP는 HTTP API의 로컬 CLI 토큰을 무제한 CLI 또는 stdio ID로 절대 받아들이지 않습니다.
- 모든 tailnet 장치에서 에이전트를 가리키도록 설정합니다:
claude mcp add --transport http fsonos https://<mac>.<tailnet>.ts.net:8443/mcp
curl https://<mac>.<tailnet>.ts.net/zones
전체 워크스루(launchd, 방화벽, tailnet 정책 부여, Spotify 로그인)는 docs/DEPLOY.md에 있습니다.
에이전트용