Join the Agent Deck Discord Server!
Discord에서 Agent Deck 커뮤니티 확인, 357명과 자유 음성·텍스트 채팅 이용
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기A local Bluetooth intercom between headsets connected to a Linux computer
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A local Bluetooth Classic intercom with selectable half-duplex and full-duplex
modes for two or more headsets on Linux. BlueZ manages Bluetooth; PipeWire and
WirePlumber provide headset audio. Each headset microphone routes to other
configured headsets in its talk groups, never back to itself. With no talk groups
configured, all headsets share the original all-to-all intercom. By default, bt-intercom automatically matches each headset's play/pause button
and keeps its microphone muted until the button is held.README 전문 보기 ↗A local Bluetooth Classic intercom with selectable half-duplex and full-duplex modes for two or more headsets on Linux. BlueZ manages Bluetooth; PipeWire and WirePlumber provide headset audio. Each headset microphone routes to other configured headsets in its talk groups, never back to itself. With no talk groups configured, all headsets share the original all-to-all intercom. By default, bt-intercom automatically matches each headset's play/pause button and keeps its microphone muted until the button is held. Half-duplex queues talk requests and allows only one headset microphone per talk group at a time. Friends or coworkers can pair their headsets with the same Linux host to join the conversation.
Watch the illustrative CLI demo. For headset wearers, see the headset user briefing.
bluetoothctl), PipeWire (pw-dump,
pw-cli, pw-play)
and WirePlumber. Run as the same user as PipeWire, not with sudo.--transport hfp (the full-duplex default) uses the headset-head-unit
profile and leaves profiles unchanged. Check devices with wpctl status;
select the duplex profile with wpctl set-profile DEVICE_ID PROFILE_INDEX.
The half-duplex default --transport sco-a2dp also requires A2DP playback on every headset.Raspberry Pi Zero W, Zero 2 W, Pi 3, Pi 4 and Pi 5 have onboard Bluetooth Classic radios. Pi 1, Pi 2 and the original Pi Zero need a compatible USB adapter. The supported Linux artifact targets for these models are listed below.
A paired/connected device is not necessarily an available voice channel.
HFP/HSP duplex audio uses a synchronous SCO/eSCO link; A2DP playback uses an
asynchronous ACL link. By default, bt-intercom budgets one simultaneous
SCO/eSCO audio link per Bluetooth controller, including a Pi's onboard radio.
Override this with run --sco-limit N only after verifying that exact
controller, firmware, and headset combination. Bluetooth Classic permits more
than one synchronous link in some configurations, but neither the number of
paired devices nor the number of PipeWire microphone ports proves SCO capacity.
For full-duplex with N headsets, use N independent Bluetooth controllers by
default and pair one headset to each; a verified higher --sco-limit can
increase the capacity budget per controller. Merely plugging in extra USB
adapters does not move existing bonds or distribute audio. For fewer controllers, see
SCO/A2DP half-duplex transport below. Additional
radios still share the 2.4 GHz spectrum; bandwidth, interference, USB power and
firmware can prevent reliable audio even with one headset per adapter.
If BlueZ reports too few controllers to provide two SCO/eSCO slots under the
configured limit, run rejects full-duplex; use a verified higher limit or
half-duplex with --ptt for every headset.
For example, on Raspberry Pi OS with PipeWire packages:
sudo apt install bluez pipewire pipewire-bin wireplumber libspa-0.2-bluetooth
systemctl --user status pipewire wireplumber
Start PipeWire and WirePlumber for your login user if they are not running. Build on the Pi:
cargo install --path . --locked
Alternatively, build and install the executable directly:
cargo build --release --locked
install -Dm755 target/release/bt-intercom ~/.local/bin/bt-intercom
Alternatively, download the executable from a successful build artifact and
install it as ~/.local/bin/bt-intercom. Each build also publishes .deb and
.rpm packages for the same target.
Completions are generated from the CLI definition at runtime. Save the output in the completion directory for your shell:
mkdir -p ~/.local/share/bash-completion/completions
bt-intercom completions bash > ~/.local/share/bash-completion/completions/bt-intercom
mkdir -p ~/.zfunc
bt-intercom completions zsh > ~/.zfunc/_bt-intercom
mkdir -p ~/.config/fish/completions
bt-intercom completions fish > ~/.config/fish/completions/bt-intercom.fish
mkdir -p ~/.config/elvish/lib
bt-intercom completions elvish > ~/.config/elvish/lib/bt-intercom.elv
For Zsh, add fpath=(~/.zfunc $fpath) before compinit in ~/.zshrc. For
Elvish, add use bt-intercom to ~/.config/elvish/rc.elv. Restart the shell
after configuring completions.
Runtime diagnostics are written to stderr and respect RUST_LOG. The default
level is info; add --verbose before or after a command (for example,
bt-intercom run --verbose) to enable debug diagnostics for subprocess
execution and routing decisions. An explicit RUST_LOG overrides this default;
set RUST_LOG=warn to show warnings only. -v remains an alias for --version.
A systemd user service sends stdout and stderr to the journal.
The terminal control panel uses semantic colors when the terminal supports
them; it respects NO_COLOR, and uses a plain style when color is unavailable.
The live dashboard only clears the screen when its output is an interactive
terminal.
Cargo.toml includes packaging metadata for the binary, man page, runtime
requirements, an AppArmor profile, and a systemd user unit. The packages install
the profile at /etc/apparmor.d/usr.bin.bt-intercom; AppArmor must be enabled
on the host for it to be enforced. The profile allows the default configuration
and runtime paths, PipeWire and BlueZ access, and read access to PTT input
devices and their sysfs identity for --ptt auto. Custom XDG_CONFIG_HOME
paths may need a local profile adjustment.
Custom TMPDIR paths may also need an adjustment for temporary PTT audio.
Install cargo-deb and
cargo-generate-rpm, then build packages with cargo deb and
cargo generate-rpm (add --target TARGET for a configured cross-compilation
target). The .deb is written to target/debian/; the .rpm is written to
target/TARGET/generate-rpm/ when a target is specified, or
target/generate-rpm/ otherwise. CI publishes both packages for each supported
target.
Install the package with your distribution's package manager. The systemd user unit is installed but not enabled automatically. After configuring the saved headset network, start it with:
systemctl --user daemon-reload
systemctl --user enable --now bt-intercom.service
The packaged unit runs /usr/bin/bt-intercom; the example unit below remains
for manual installations under ~/.local/bin.
For Raspberry Pi, choose an artifact for both the model and OS architecture:
| Raspberry Pi model | OS | Artifact target |
|---|---|---|
| Pi 1, original Zero, Zero W | 32-bit | arm-unknown-linux-gnueabihf (ARMv6) |
| Pi 2, Zero 2 W, Pi 3, Pi 4 | 32-bit | armv7-unknown-linux-gnueabihf (ARMv7) |
| Zero 2 W, Pi 3, Pi 4, Pi 5 | 64-bit | aarch64-unknown-linux-gnu |
Pi 5 should use a 64-bit OS. ARMv6 binaries also run on compatible 32-bit ARMv7 systems; ARMv7 binaries do not run on ARMv6. Artifacts require a compatible glibc. These targets describe supported Raspberry Pi builds; other Linux systems can build natively with Cargo.
With two HFP/HSP headsets, scan for devices, pair each headset in an interactive terminal, check audio readiness, then start the intercom:
bt-intercom scan
bt-intercom pair AA:BB:CC:DD:EE:01
bt-intercom pair AA:BB:CC:DD:EE:02
bt-intercom status AA:BB:CC:DD:EE:01 AA:BB:CC:DD:EE:02
bt-intercom run AA:BB:CC:DD:EE:01 AA:BB:CC:DD:EE:02
Replace the example addresses with those reported by your scan. If status says
duplex audio is unavailable, check the PipeWire profile and Bluetooth connection
before running; see Requirements and
Troubleshooting. Run bt-intercom --help or
bt-intercom run --help for options.
pair needs an interactive terminal. At the bluetoothctl prompt, enter the
printed pair ADDRESS command, handle any PIN or confirmation prompt, then
enter quit.status reports Bluetooth disconnected, run connects headsets automatically; otherwise connect the
headset with BlueZ. If duplex audio is unavailable, check that the headset is
connected and using its HFP/HSP headset profile in PipeWire.Show the installed version and UTC build datetime:
bt-intercom --version
Put each headset in pairing mode, then scan and pair it:
bt-intercom scan --seconds 20
bt-intercom pair AA:BB:CC:DD:EE:01
bt-intercom pair AA:BB:CC:DD:EE:02
Run pair in an interactive shell. At the BlueZ KeyboardDisplay prompt, enter
the printed pair ADDRESS command, answer any PIN or confirmation prompts, then
type quit; the program verifies pairing, trusts and connects the device. If a
headset is incompatible with the agent, pair it through the desktop's Bluetooth
UI or bluetoothctl. Check Bluetooth connection and PipeWire duplex availability:
bt-intercom status AA:BB:CC:DD:EE:01 AA:BB:CC:DD:EE:02
status reports each headset's Bluetooth connection state, name/address, and
PipeWire microphone and speaker ports. A failed Bluetooth query is reported
separately from an unknown connection state; the command does not connect devices
or create routes. Pair and trust only devices you have permission to connect;
friends or coworkers can pair their headsets with
the host. BlueZ stores pairing credentials; this program stores only headset
addresses.
Start the intercom with the paired addresses:
bt-intercom run AA:BB:CC:DD:EE:01 AA:BB:CC:DD:EE:02
Addresses are saved in ${XDG_CONFIG_HOME:-~/.config}/bt-intercom/headsets.
If XDG_CONFIG_HOME is unset or not an absolute path, they are saved under
~/.config/bt-intercom/headsets. Later, run bt-intercom run to
restore them. run connects disconnected headsets at startup and retries every 30 seconds;
use --no-connect if another Bluetooth manager keeps them connected. Reconnection checks and
attempts run for up to two headsets concurrently, so a slow connection does not
block every other headset. Routing is polled every two seconds by default;
change that with --interval SECONDS (minimum: 1 second).
Ctrl-C or SIGTERM closes links created by this process. Existing PipeWire links
are not modified.
Shutdown also cancels and joins background workers, including idle headset
button readers. --sco-limit N sets the maximum SCO/eSCO capacity budget per
controller (default: 1). In sco-a2dp transport, it limits simultaneous talkers
sharing a controller. The default HFP transport leaves headset profiles
unchanged; it warns when observed headset usage exceeds the selected budget but
cannot redistribute or disconnect Bluetooth devices.
Run settings, saved-network mappings and talk groups are validated before workers
start. Invalid settings or unavailable button inputs do not replace the saved
network with explicitly supplied addresses.
Remove a headset from the saved intercom network with bt-intercom remove ADDRESS.
This does not disconnect or unpair it; restart a running intercom or service
for the change to take effect. Removing the last address clears the saved
network.
Best-effort beeps confirm when an intercom route becomes active. Their temporary
WAV file is created once at startup and reused for every beep. It is placed
under XDG_RUNTIME_DIR when that is an absolute path, falling back to the
system temporary directory otherwise, and is removed during shutdown.
Start the terminal control panel after saving a headset network:
bt-intercom panel
The panel shows Bluetooth connection and signal status plus PipeWire duplex
availability. Use Tab to switch between the talk-group and member lists,
arrow keys to move, Home/End to jump to the first/last item, n to create a
group, d to remove the selected group, and Space to toggle headset membership.
The group list shows each group's current member count. Enter saves a new group
name; Esc cancels name entry or quits the panel. Group settings are saved to
${XDG_CONFIG_HOME:-~/.config}/bt-intercom/talk-groups.json; changes are
picked up by a running bt-intercom run process on its next routing update.
A headset's microphone is routed only to other headsets sharing at least one
group with it. Headsets not assigned to a group are not routed when any groups
exist. With no groups, routing remains all-to-all. Removing the final group
requires confirmation because it restores all-to-all routing. The panel is
included in the standard CLI and cross-compiled packages.
run defaults to half-duplex.
Select --mode full-duplex or --mode half-duplex explicitly to override.
run automatically matches a play/pause input
device for each configured headset and starts each microphone in PTT mode. The
headsets must be connected when run starts; startup fails if an input device
cannot be matched uniquely. In full-duplex, each headset can independently
switch between PTT and always-open by pressing the play/pause button three times
within one second. Switching back to PTT mutes the microphone when the button is
released. These choices reset when the process exits or that headset loses
duplex audio.
Map each headset to its own Linux input event device. PTT listens only for
KEY_PLAYPAUSE press/release events (code 164), not call/answer buttons or
keyboard input:
bt-intercom run AA:BB:CC:DD:EE:01 AA:BB:CC:DD:EE:02 \
--ptt AA:BB:CC:DD:EE:01=/dev/input/event4 \
--ptt AA:BB:CC:DD:EE:02=/dev/input/event5
Automatic matching is the default. To override it, provide an event path for
each headset as shown above. You can also use --ptt auto to request matching
explicitly for every listed headset, or --ptt AA:BB:CC:DD:EE:01=auto for one.
The kernel labels each headset's button device with its Bluetooth address.
In PTT mode, hold a headset's play/pause button to transmit from its microphone; release to mute it. A double beep confirms when its microphone route is active.
Half-duplex is the default; --mode half-duplex is optional. Button mappings
are matched automatically unless given for every headset. Hold play/pause to request a turn: if another
headset is transmitting, your request waits in first-in, first-out order.
Keep holding while queued and wait for the double beep before talking.
Releasing the button cancels a queued request or ends your turn, allowing the
next waiting headset in that group to transmit. Each configured talk group has
an independent queue, so separate groups can have active talkers at the same
time. A headset in multiple groups requests a turn in each and is routed only
to groups where it currently holds the floor. With no talk groups configured,
all headsets share one queue. Triple presses do not enable always-open
microphones in half-duplex.
Half-duplex routing failures stop the run and release its owned links rather
than risk leaving the previous talker active. Existing external links remain
untouched, so exclusivity applies only to routes managed by this process.
When a mapped headset loses duplex audio, its request and always-open choice
are reset; after reconnecting, release and press again to talk.
--transport hfp with half-duplex only gates PipeWire links: with HFP, listeners still need SCO/eSCO for their speakers. It does not solve
a shared controller's synchronous-link limit.
Half-duplex defaults to --transport sco-a2dp (full-duplex defaults to hfp),
so no extra flags are needed. Explicit form:
bt-intercom run AA:BB:CC:DD:EE:01 AA:BB:CC:DD:EE:02 \
--mode half-duplex --transport sco-a2dp \
--ptt AA:BB:CC:DD:EE:01=/dev/input/event4 \
--ptt AA:BB:CC:DD:EE:02=/dev/input/event5
This transport selects advertised PipeWire profile indices rather than assuming
fixed numbers. Idle/listening headsets use A2DP; granted talkers use HFP/HSP for
their microphones, up to --sco-limit per controller. Before granting another
SCO link on a controller, the program closes its old routes, switches listeners
to A2DP, and waits until the profile changes are observed before enabling the
new talker's HFP profile.
For A2DP listeners, it prefers the standard SBC codec rather than SBC-XQ or
another advertised codec, which is the bandwidth-conscious choice for the
intercom's mono voice. WirePlumber still negotiates the SBC rate, bitrate, and
channel mode, so this preference alone does not guarantee a fixed bitrate or a
mono encoded stream.
Only allowlisted Bluetooth devices are changed. Missing controller identity or
required HFP or SBC profiles produce actionable errors instead of assuming extra
capacity.
Temporary absence of microphone ports during a switch does not cancel a held
PTT request. A genuine device disappearance retires the request.
Status/dashboard duplex availability still describes HFP microphone and speaker
ports: an idle A2DP listener is intentionally playback-only, not duplex-ready.
Talk-group routing remains isolated. Each group keeps its FIFO queue, but groups sharing a radio also share its configured SCO slots. Different radios can grant independent talkers. A headset cannot listen through A2DP while its own HFP profile is active, so overlapping groups may have listeners temporarily unable to receive; no simultaneous HFP+A2DP capability is assumed. Profiles are left in their last selected state on exit, rather than restoring multiple HFP profiles and immediately recreating the capacity conflict. Owned links are always released; existing external links and other applications' profile policy are not controlled. Avoid running another profile manager against these devices.
Hardware validation is required. HFP/A2DP switching can interrupt audio and take seconds, and A2DP adds codec/buffering latency. The headset must preserve play/pause press and release events across profile changes; some do not. WirePlumber automatic profile switching may compete with this policy. Disable competing automatic switching for your deployment and test floor handoffs, queued releases, disconnects, cancellation, and shutdown on the actual hardware. This is not seamless voice conferencing, and ACL/A2DP capacity is not unlimited.
Use an interactive bluetoothctl session to select each controller before
scanning, pairing, trusting and connecting its assigned headset:
list
select CONTROLLER_MAC
scan on
pair HEADSET_MAC
trust HEADSET_MAC
connect HEADSET_MAC
scan off
quit
Repeat with another controller for the next headset. If a headset was already
paired to the wrong adapter, remove that bond deliberately before re-pairing;
the intercom never migrates bonds. Confirm controller paths and audio profiles
with pw-dump and wpctl status.
Automatic reconnection also uses BlueZ's default controller. Pass --no-connect for multi-controller
deployments and use an adapter-aware Bluetooth manager to maintain connections,
or reconnect manually after select in the same bluetoothctl session.
Selections are process-local; selecting in one invocation does not configure
the next invocation. Ordinary info/connect object-path arguments are not
portable substitutes for controller selection.
Auracast is Bluetooth LE Audio broadcast, not an alternative codec or profile for Bluetooth Classic A2DP/HFP. It requires a broadcast-capable LE Isochronous controller, compatible firmware/kernel/BlueZ/PipeWire support and Auracast receivers. An onboard Classic-capable Pi radio or an ordinary A2DP headset does not establish those capabilities. This release does not configure broadcast sources, broadcast discovery/assistant services, or LE Audio microphone unicast; it never silently treats A2DP or arbitrary LE nodes as Auracast.
The transport design follows upstream documentation, not a claimed universal one-SCO hardware specification:
btmon,
rather than interpreting a successful generic connect as audio readiness.Connect succeeds when at least one profile
connects, not necessarily the voice profile.Check the documentation matching your installed versions. On real hardware, verify the configured number of simultaneous SCO/eSCO streams per assigned radio, microphone audio reaching every intended listener (and no other groups), profile handoff timing, and ACL/A2DP stability under the maximum listener load. A linked PipeWire graph and a confirmation beep establish routing state, not measured voice quality or certified controller capacity.
Find event devices with evtest or libinput debug-events.
Use stable /dev/input/by-id/ or /dev/input/by-path/ paths where available.
The running user needs permission to read the devices, and the Bluetooth stack
must expose distinct play/pause press and release events. Missing or unreadable
devices stop PTT from starting; a device failure while running exits and cleans
up. PTT is not a privacy boundary: a failed mute can leave audio active until a
retry succeeds.
Add --dashboard to run for a live terminal view of the selected half- or
full-duplex mode, per-headset PTT/floor and queue state, Bluetooth status, duplex
availability, owned route counts, and optional battery level and RSSI. Because the dashboard runs
inside run, it reflects that process's mode directly without inter-process
communication. It requires an
interactive terminal on stderr and is not intended for a service. Route counts
show observed, channel-compatible links between different configured headsets,
not measured speech or audio quality. Transmit confirmations use the same
route-readiness checks. Both terminal views show failed Bluetooth status queries
as unknown and report the polling error; a later successful poll clears it.
Battery and RSSI come from bluetoothctl info; BlueZ only reports them if the
headset exposes them (battery via HFP may need bluetoothd --experimental, and
RSSI for connected BR/EDR links is often absent). Missing values show as "—".
An example full-duplex user service is in examples/bt-intercom.service. Pair
and configure the headsets first, then install and enable it:
mkdir -p ~/.config/systemd/user
cp examples/bt-intercom.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now bt-intercom.service
journalctl --user -u bt-intercom.service -f
The unit uses run and saved addresses; it does not enable PTT. The
user's PipeWire/WirePlumber session must be available. To start before login,
enable lingering with sudo loginctl enable-linger "$USER" and configure
WirePlumber's Bluetooth seat policy for headless use. Stop the unit to release
its routes:
systemctl --user stop bt-intercom.service
Bluetooth connection capacity and audio behavior depend on the adapter,
firmware, OS and headset; simultaneous HFP/HSP connections are not guaranteed.
The host needs no local microphone or speaker, but this is not a network
intercom. Profile switching is the half-duplex default (--transport sco-a2dp); there is no
automatic bond migration, Auracast broadcast, echo cancellation or audio
processing. Headsets need acoustic isolation to avoid
feedback. Host tests use synthetic PipeWire graphs and do not verify Bluetooth
hardware or audio transport; test the actual devices, reconnects, PTT and
shutdown behavior before relying on a deployment.
Run the same checks as CI:
cargo fmt --check
cargo clippy --all-targets --locked -- -D warnings
cargo test --locked