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
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
# TuneBlade Controller
Home Assistant custom integration for controlling AirPlay devices connected to a TuneBlade server.
TuneBlade is a simple Windows tray utility that lets you stream system-wide audio to AirPort Express, Apple TV, AirPlay enabled speakers and HiFi receivers, and to AirPlay audio receiving applications such as ShairPort, XBMC/Kodi. Connect an Alexa/Google/other speaker to the host and output via AirPlay.

# TuneBlade Remote
Home Assistant custom integration for controlling AirPlay devices via TuneBlade Remote.

## Setup
Install TuneBlade on a Windows device.
Ensure Romote Control is set to on in the TuneBlade settings.

Search HACS for TuneBlade and download.
Installation is then via the Integrations Page of Home Assistant.

Ensure Romote Control is on in TuneBlade settings.
Restart TuneBlade to ensure it's broadcasting.
Restart TuneBlade to ensure it's broadcasting (seems to stop advertising Bonjour after a while).

TuneBlade should be automatically discovered however, manually configuration is possible with the host (without http://) and the port.

The integration will add a simple switch for turning the connection on/off, and a media player which can be used in the same way but can also control volume (from TuneBlade).

## Master Volume Control
The Master control setting must be enabled in TuneBlade settings.
The Master control setting must be enabled in TuneBlade settings. A device named Master will then also be available.

4 changes: 2 additions & 2 deletions custom_components/tuneblade/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Flo

data_schema = vol.Schema(
{
vol.Required("host"): str,
vol.Required("port", default=443): int,
vol.Required("host", default='localhost'): str,
vol.Required("port", default=54412): int,
vol.Optional("name", default="TuneBlade"): str,
}
)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/tuneblade/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
NAME = "TuneBlade"
DOMAIN = "tuneblade"
DOMAIN_DATA = f"{DOMAIN}_data"
VERSION = "2025.06.5"
VERSION = "2025.06.6"
ISSUE_URL = "https://github.com/spycle/tuneblade/issues"

# Icons
Expand Down
2 changes: 1 addition & 1 deletion custom_components/tuneblade/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"mqtt": [],
"requirements": [],
"usb": [],
"version": "2025.06.5",
"version": "2025.06.6",
"zeroconf": [
{
"type": "_http._tcp.local.",
Expand Down
6 changes: 3 additions & 3 deletions custom_components/tuneblade/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ class TuneBladeHubMediaPlayer(CoordinatorEntity, MediaPlayerEntity):
def __init__(self, coordinator):
super().__init__(coordinator)
self.device_id = "MASTER"
self._attr_name = "TuneBlade Master"
self._attr_unique_id = "tuneblade_master"
self._attr_name = "Master"
self._attr_unique_id = "tuneblade_master_media_player"
self._attr_volume_level = None
self._attr_state = MediaPlayerState.OFF
self._attr_supported_features = (
Expand Down Expand Up @@ -125,7 +125,7 @@ def extra_state_attributes(self):
@property
def device_info(self):
return {
"identifiers": {(DOMAIN, self.device_id)},
"identifiers": {(DOMAIN, "MASTER")},
"name": self._attr_name,
"manufacturer": "TuneBlade",
"entry_type": "service", # Mark as hub/service device
Expand Down
4 changes: 2 additions & 2 deletions custom_components/tuneblade/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class TuneBladeHubSwitch(CoordinatorEntity, SwitchEntity):

def __init__(self, coordinator):
super().__init__(coordinator)
self._attr_name = "TuneBlade Master Switch"
self._attr_name = "Master"
self._attr_unique_id = "tuneblade_master_switch"

@property
Expand Down Expand Up @@ -134,7 +134,7 @@ def available(self):
def device_info(self):
return {
"identifiers": {(DOMAIN, "MASTER")},
"name": "TuneBlade Hub",
"name": self._attr_name,
"manufacturer": "TuneBlade",
"entry_type": "service",
}
Expand Down
7 changes: 4 additions & 3 deletions custom_components/tuneblade/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
"description": "TuneBlade Remote",
"data": {
"host": "Host",
"port": "Port"
"port": "Port",
"name": "Name"
}
},
"confirm": {
"title": "Confirm TuneBlade Device",
"description": "TuneBlade was discovered at {ip}. Do you want to add it?"
"title": "Confirm TuneBlade Remote",
"description": "TuneBlade was discovered at {ip}. Do you want to add {name}?"
}
},
"abort": {
Expand Down
Loading