A .NET 10 control system for Bang & Olufsen audio equipment via the Masterlink bus. BeoControl bridges legacy B&O hardware with modern software, supporting both USB PC2 controllers and ESP32-based Beo4 remote emulators over Serial/USB or Bluetooth LE.
BeoControl lets you control B&O Masterlink devices (TV, Radio, CD, DVD, etc.) from a PC, offering:
- Unified command interface across heterogeneous B&O hardware
- Audio control — volume, bass, treble, balance, loudness
- Source switching — TV, Radio, CD, DVD, SAT, PC and more
- Multi-transport support — USB (PC2), Serial/USB (ESP32), Bluetooth LE (ESP32)
- Spotify playback control — optional Spotify integration for play, pause, next, previous and now-playing info
- UI-agnostic design — the core logic is fully decoupled from the UI layer, making it straightforward to add new frontends
- Shared settings — all UIs share a single settings file (
%APPDATA%\BeoControl\beocontrol.settings.json) and auto-connect on startup
The hardware and adapter layers are completely independent of any user interface. The IDevice abstraction is the only contract a UI needs to implement against, so different frontends can be built without touching the core logic:
| Frontend | Status | Description |
|---|---|---|
| Terminal (TUI) | ✅ included | Full-featured console UI using RazorConsole — great for headless servers and SSH sessions |
| Web (Blazor Server) | ✅ included | ASP.NET Core Blazor Server UI — browser-based remote control |
| Desktop (MAUI) | ✅ included | Windows tray app — sits in the system tray, pops up above the taskbar on click, xcopy deploy |
| REST / API | 🔧 possible | Wrap IDevice in a minimal ASP.NET Core API to integrate with Home Assistant, shortcuts, scripts, etc. |
The architecture is designed so that adding a new UI is simply a matter of referencing the Interfaces and Adapters packages and calling device.SendCommand(...).
Any UI ──→ IDevice ──→ Adapter ──→ Transport ──→ B&O Hardware
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ UI Layer │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ ┌──────────────────┐│
│ │ BeoControl │ │ BeoControl │ │ BeoControl MAUI │ │Beoport / Beolink ││
│ │ TUI │ │Blazor Server │ │ Windows tray app │ │ ControlExample ││
│ │ │ │ │ │ IOs / OSX /Android │ │ (a simple demo) ││
│ │ │ │ │ │ │ │ ││
│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────────┘ └─────────┬────────┘│
└─────────┼─────────────────┼─────────────────────┼─────────────────────────────┼─────────┘
└─────────────────┴───────┬─────────────┴─────────────────────────────┘
│ IDevice
┌─────────────────────────┴────────────────────────┐
│ Adapter Layer │
│ │
│ ┌──────────────────────┐ ┌──────────────────┐ │
│ │ Beo4Device │ │ Pc2Device │ │
│ │ (Serial / BLE) │ │ (USB PC2) │ │
│ └──────────┬───────────┘ └─────────┬────────┘ │
└─────────────┼────────────────────────┼───────────┘
│ │
┌─────────────┴───────┐ ┌─────────────┴──────────┐
│ Transport Layer │ │ Hardware Layer │
│ SerialTransport │ │ Pc2Core (event loop) │
│ BluetoothTransport │ │ Pc2Mixer (audio HW) │
│ (BLE NUS) │ │ Beolink (Masterlink) │
└──┬──────────────┬───┘ └───────────┬────────────┘
│ │ │
┌────────┴───┐ ┌───────┴────┐ ┌────────┴────┐
│ M5 Atom S3 │ │ M5 Stamp S3│ │ Beolink PC2 │
│ (Serial / │ │ (Serial / │ │ (USB) │
│ BLE) │ │ BLE) │ │ │
└─────┬──────┘ └─────┬──────┘ └──────┬──────┘
│ IR │ IR │ Masterlink
▼ ▼ ▼
| Project | Description |
|---|---|
Interfaces |
Core abstractions: IDevice, ITransport, BeoCommands, Pc2Commands |
Adapters/Beo4Adapter |
Adapts ESP32 Beo4 (Serial or BLE) to IDevice |
Adapters/Pc2Adapter |
Adapts PC2 USB hardware to IDevice |
Hardware/dotnet_pc2 |
Low-level Masterlink/PC2 protocol implementation |
Hardware/esp32_beo4 |
ESP32 firmware (PlatformIO) for Beo4 remote emulation |
AddIns/SpotifyApi/Spotify |
Shared Spotify playback integration library |
AddIns/SpotifyApi/SpotifyHost |
Console host for Spotify authentication and playback control |
UI/BeoControlTUI |
Terminal UI using RazorConsole |
UI/BeoControlBlazor |
Web UI using ASP.NET Core Blazor Server |
UI/BeoControlMaui |
Windows tray app using .NET MAUI + Blazor Hybrid |
UI/PC2ControlExample |
Minimal console app demonstrating direct PC2 adapter usage |
The PC2 is a USB device (VID 0x0CD4, PID 0x0101) that bridges the PC to the B&O Masterlink bus. BeoControl uses LibUsbDotNet for low-level USB communication and implements the full Masterlink telegram protocol.
The PC2 implementation and Masterlink protocol knowledge is based on the work of Herwin Jan Steehouwer on the KPC2 project, and on beoported by Tore Sinding Bekkedal — a big thank you to both for reverse-engineering and documenting the PC2 and Masterlink protocol.
The ESP32 firmware emulates a Beo4 IR remote and communicates with BeoControl over Serial/USB or Bluetooth LE (Nordic UART Service). It runs on an M5 Atom S3 and is based on the excellent esp32_beo4 project by aanban — many thanks for this great foundation!
- .NET 10 SDK
- A B&O Masterlink system with either:
- A PC2 USB device (connect to USB, install LibUSB driver), or
- An ESP32 Beo4 device (M5 Atom S3 with esp32_beo4 firmware)
dotnet build BeoControl.slndotnet run --project UI/BeoControlTUIdotnet run --project UI/BeoControlBlazor/BeoControlBlazorServer
# Open http://localhost:5000 in a browserBuild and run from Visual Studio, or publish for xcopy deployment:
cd UI/BeoControlBlazor/BeoControlMaui
dotnet publish -f net10.0-windows10.0.19041.0 -c Release -r win-arm64 --self-contained true -p:PublishTrimmed=falseCopy the publish\ folder to any Windows machine — no installer or runtime install needed.
The repository includes a GitHub Actions workflow at .github/workflows/release-maui-windows.yml that builds release assets and attaches them to a GitHub release.
- Push a tag like
v1.0.0to trigger the workflow automatically. - Or run the
Release MAUI Windowsworkflow manually and provide a tag such asv1.0.0. - The MAUI app publishes Velopack installers for
win-x64andwin-arm64, each inself-containedandframework-dependentvariants. - BeoControlWebKit publishes Velopack packages for
linux-x64andlinux-arm64, each inself-containedandframework-dependentvariants. - Windows installer assets are uploaded as
BeoControl-<runtime>-<package-mode>-Setup.exe. - Linux AppImages are uploaded as
BeoControl-<runtime>-<package-mode>.AppImage. - Velopack update feed files (
.nupkg,releases.*.json,assets.*.json) are published to GitHub Pages under/updates.
The workflow creates the GitHub release automatically if it does not already exist.
Download and run the Windows Velopack installer from the GitHub release.
- Download the Windows installer for your architecture:
BeoControl-win-x64-self-contained-Setup.exeBeoControl-win-x64-framework-dependent-Setup.exeBeoControl-win-arm64-self-contained-Setup.exeBeoControl-win-arm64-framework-dependent-Setup.exe
- Run the installer.
- Velopack installs the app to
%LocalAppData%\BeoControl. - The installer creates Start menu and Desktop shortcuts and launches the app.
Windows auto-update is supported through Velopack. Choose self-contained when the target machine should not need a preinstalled .NET runtime, or framework-dependent when the required runtime is already managed separately.
Download the Linux .AppImage from the GitHub release.
- Download the AppImage for your architecture:
BeoControl-linux-x64-self-contained.AppImageBeoControl-linux-x64-framework-dependent.AppImageBeoControl-linux-arm64-self-contained.AppImageBeoControl-linux-arm64-framework-dependent.AppImage
- Make it executable with
chmod +x <file>.AppImage. - Start it directly or from a
.desktoplauncher that points to the AppImage.
The AppImage runs from its current location; it is not installed to a fixed system directory by default. Linux auto-update is supported when the user keeps launching the Velopack-produced AppImage for the same architecture and package mode. Choose self-contained when the machine should not need a preinstalled .NET runtime, or framework-dependent when the required runtime is already managed separately.
The MAUI project includes an Android target, and the release workflow now builds an Android APK.
Android installation remains a manual distribution scenario:
- Download the Android APK from the workflow artifacts, or from GitHub Releases when Android signing secrets are configured.
- Install it on the device by sideloading or through an app store / MDM workflow.
Velopack auto-update does not apply to Android. Android updates need the normal Android distribution path, such as Play Store updates, managed device deployment, or installing a newer APK manually. A stable signing key is required if newer APKs should install as upgrades over older ones.
The MAUI project also contains iOS and MacCatalyst targets, but this repository does not currently publish installable release assets for those platforms.
dotnet run --project UI/PC2ControlExample/PC2ControlExampleBeoControl also includes an optional Spotify integration:
- standalone Spotify console control via
SpotifyHost - commands:
play,pause,next,prev - now-playing information with title, artist and play/pause state
- Blazor Beo4 integration: when the active source is
A.TAPE, Beo4 transport keys can control Spotify and show track info in the display
Build and run the Spotify host:
dotnet run --project AddIns/SpotifyApi/SpotifyHostCreate AddIns/SpotifyApi/SpotifyHost/spotifysettings.json with your Spotify app settings:
{
"ClientId": "your-spotify-client-id",
"RedirectUri": "http://127.0.0.1:5543/callback",
"PreferredDeviceName": "Musikanlage"
}Notes:
- you need a Spotify app Client ID
- the redirect URI must match the URI configured in your Spotify app
- a Spotify playback device must already be available (desktop app, phone, web player, etc.)
- the Spotify token cache is stored in the BeoControl app-data folder (for example
~/.config/BeoControl/spotify-token.jsonon Linux)
# Run targeting the non-Windows framework
dotnet run --project UI/BeoControlTUI --framework net10.0
# Grant serial port access (one-time, requires re-login)
sudo usermod -aG dialout $USER
# Grant non-root access to the PC2 USB device (VID=0x0CD4, PID=0x0101)
sudo sh -c 'printf "%s\n" \
"SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0cd4\", ATTR{idProduct}==\"0101\", MODE=\"0666\"" \
> /etc/udev/rules.d/99-beocontrol-pc2.rules'
sudo udevadm control --reload-rules
sudo udevadm trigger
# Unplug and reconnect the PC2 after reloading the rules| Command | Description |
|---|---|
/port |
Connect via auto-detected serial port |
/port scan |
List available serial ports |
/bt |
Connect via Bluetooth LE |
/bt scan |
Scan for BLE Beo4 devices |
/bt-last |
Reconnect to last BLE device |
/pc2 |
Connect via PC2 USB |
/help |
Show all available commands |
/debug |
Toggle debug output |
/clear |
Clear screen |
/exit |
Exit application |
Once connected, type any B&O command directly (e.g. tv, radio, vol+, standby).
Sources: tv, radio, cd, dvd, dvd2, phono, sat, pc
Volume: vol+, vol-, mute, loudness
Tone: bass+, bass-, treble+, treble-, balance+, balance-
Navigation: up, down, left, right, menu, exit, select
Colors: red, green, blue, yellow
Power: standby, allstandby
The Windows tray app provides a compact always-available remote:
- Starts hidden to the system tray on launch
- Click tray icon → window appears just above the taskbar, centered on the icon
- X button hides to tray (does not exit); right-click for Show / Exit
- Auto-connects on startup to the last used device (shared settings with Blazor Server)
- Remembers window size — resize once, persisted across restarts
- Connection indicator — tray icon and in-app bar show green/red connection state
- Xcopy deployment — no installer needed; copy the
publish\folder to any Windows ARM64 or x64 machine
All UIs share a single settings file so the last device is remembered across apps:
| OS | Path |
|---|---|
| Windows | %APPDATA%\BeoControl\beocontrol.settings.json |
| Linux | ~/.config/BeoControl/beocontrol.settings.json |
| macOS | ~/Library/Application Support/BeoControl/beocontrol.settings.json |
The TUI uses a separate file (beocontrol-tui.settings.json) in the same folder.
If you place the ESP32 very close to the B&O IR receiver, a single IR LED wired directly to the M5 Atom S3 is all the hardware you need:
┌──────────────────┐
│ M5 Atom S3 │
│ │
│ G38 ────────────┼──── LED anode (+)
│ │ │ TSHA 6203 IR LED
│ GND ────────────┼──── LED cathode (-)
│ │
└──────────────────┘
G38 drives the IR LED signal. GND is the return path. No resistor is strictly required at very short range, but a 33–100 Ω resistor in series with the LED is recommended to protect the pin.
For greater range, a BC847 NPN transistor switches a higher current through three IR LEDs in parallel, each with its own R10 current limiting resistor, powered from V+:
┌──────────────────┐
│ 5V─┼───────────────────────────────┐
│ │ │
│ M5 Atom S3 │ │
│ │ ┌─────────┼─────────┐
│ G38─┼──┬───────────┐ R22 R22 R22 (22 Ω each)
│ │ | │ │ │ │
│ │ R10k* │ LED1 LED2 LED3 (TSHA 6203)
│ GND─┼──| │ │ │ │
└──────────────────┘ │ │ └─────────┴─────────┘
│ R220 │
│ ┌──────┼──────────┐ │
│ │ B │ │
│ │ │ │
│ │ BC337 C─┼─────┘
└────┼─E │
└─────────────────┘
Rechts an der 5V-Leitung, runter nach GND:
5V ────────────────────────────────┐
│
┌─||─┐ 100 nF
│ │
└─||─┘ 10–47 µF
│
GND────────────────────────────────┘
G38 → R220 limits base current into the BC337. Each of the 3× TSHA 6203 IR LEDs has its own R22 resistor in series to V+. All three LED+R10 branches are in parallel, connected to the BC337 collector. GND connects to the emitter.
- aanban/esp32_beo4 — ESP32 Beo4 remote firmware. A huge thanks for this project which made ESP32-based control possible.
- toresbe/beoported — The base and inspiration for the PC2/Masterlink protocol implementation.
- Herwin Jan Steehouwer — For the KPC2 project and invaluable work on reverse-engineering the PC2 hardware and Masterlink protocol.
This project is provided as-is for personal/hobbyist use with Bang & Olufsen audio equipment.