Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion c_src/sendspin_player/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ changes, and on errors.
{"event":"pcm_tap","value":false,"ok":false,"error":"open: Permission denied"} // pcm_tap on failed
{"event":"error","kind":"alsa_configure","msg":"..."}
{"event":"error","kind":"pairing_token","msg":"..."} // non-fatal; token itself never on stdout
{"event":"error","kind":"state_dir","msg":"..."} // fatal: the identity cannot be kept (exits 1)
{"event":"error","kind":"oversize_line","msg":"..."} // a line over the cap was dropped (a bug)
{"event":"shutdown"}
```
Expand Down Expand Up @@ -306,7 +307,8 @@ X25519 public key, generates that keypair on first start, and stores it
through the persistence provider. `FilePersistence` writes each library
blob as one file in `--state-dir` (dir `0700`, files `0600`, written via
temp file + fsync + rename), so the identity survives respawns and
reboots. A state dir that cannot be created fails the process before the
reboots. A state dir that cannot be created fails the process (with a
`state_dir` error event, before `started`) before the
client starts, rather than letting it mint an identity it cannot keep. The
library only logs a failed save of a freshly generated keypair (a full or
read-only disk) and starts anyway, so the binary checks for that after
Expand Down
5 changes: 5 additions & 0 deletions c_src/sendspin_player/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,11 @@ int main(int argc, char* argv[]) {
std::string err;
if (!persistence.init(err)) {
std::fprintf(stderr, "Error: --state-dir: %s\n", err.c_str());
// The same event as a keypair that cannot be persisted: the BEAM
// side marks the output's identity as failed on it. `err` names
// the path, so it is bounded to keep the line under the budget.
emit_json("{\"event\":\"error\",\"kind\":\"state_dir\",\"msg\":\"state dir unusable: " +
json_escape(sanitize_text(err, 512)) + "\"}");
return 1;
}
}
Expand Down
41 changes: 40 additions & 1 deletion lib/universal_proxy/audio.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule UniversalProxy.Audio do
`{card_name, nil, nil}` (e.g. `{"bcm2835 Headphones", nil, nil}`).
"""

alias UniversalProxy.Audio.{PairingToken, Player, Server, Store}
alias UniversalProxy.Audio.{HomeAssistantPairing, PairingToken, Player, Server, Store}

@typedoc """
Composite identifier for an audio output:
Expand Down Expand Up @@ -195,4 +195,43 @@ defmodule UniversalProxy.Audio do
catch
:exit, _ -> {:error, :unavailable}
end

@doc """
The output reported to Home Assistant for Sendspin auto-pairing, or
`nil` when none is (or the audio subsystem is down). See
`UniversalProxy.Audio.HomeAssistantPairing`.
"""
@spec home_assistant_pairing() :: key() | nil
def home_assistant_pairing do
case HomeAssistantPairing.selection() do
{:ok, %{key: key}} -> key
_none_or_unavailable -> nil
end
catch
:exit, _ -> nil
end

@doc """
Report the output `key` to Home Assistant for Sendspin auto-pairing,
replacing any other. `{:error, :not_found}` when the output isn't
present, `{:error, :unavailable}` while the audio subsystem is down.
"""
@spec select_home_assistant_pairing(key()) ::
:ok | {:error, :not_found | :unavailable | term()}
def select_home_assistant_pairing({_, _, _} = key) do
HomeAssistantPairing.select(key)
catch
:exit, _ -> {:error, :unavailable}
end

@doc """
Stop reporting any output to Home Assistant. `{:error, :unavailable}`
while the audio subsystem is down.
"""
@spec clear_home_assistant_pairing() :: :ok | {:error, :unavailable | term()}
def clear_home_assistant_pairing do
HomeAssistantPairing.clear()
catch
:exit, _ -> {:error, :unavailable}
end
end
Loading
Loading