- Domain:
bravia_rest_api - Integration type:
device(single TV per config entry) - Min HA version: 2024.1.0
custom_components/bravia_rest_api/
├── __init__.py # Integration setup, forward platforms
├── manifest.json # Integration metadata
├── const.py # Constants, domain, default values
├── bravia_client.py # Sony Bravia REST API client
├── config_flow.py # Config wizard (IP + PSK + validation)
├── coordinator.py # DataUpdateCoordinator
├── entity.py # Base entity class
├── media_player.py # Main media player entity
├── remote.py # IRCC remote entity
├── button.py # Action buttons (reboot, blank screen, etc.)
├── select.py # Picture mode, sound output, screen rotation
├── sensor.py # System info sensors
├── switch.py # LED indicator, WoL mode toggles
├── services.yaml # Custom service definitions
├── strings.json # English UI strings
└── translations/
└── es.json # Spanish translations
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Config Flow │────>│ BraviaClient │────>│ Sony Bravia TV │
│ (validate) │ │ (REST + IRCC) │ │ (HTTP API) │
└──────┬───────┘ └────────┬─────────┘ └─────────────────┘
│ │
v v
┌──────────────┐ ┌──────────────────┐
│ Config Entry │────>│ Coordinator │
│ (IP, PSK) │ │ (polls state) │
└──────────────┘ └────────┬─────────┘
│
┌───────────────────┼───────────────────┐
v v v
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ MediaPlayer │ │ Remote │ │ Button/Sel. │
│ Entity │ │ Entity │ │ /Sensor/Sw. │
└─────────────┘ └──────────────┘ └──────────────┘
DOMAIN = "bravia_rest_api"
DEFAULT_PORT = 80
CONF_PSK = "psk"
SCAN_INTERVAL = 15 # seconds
# Platforms to load
PLATFORMS = ["media_player", "remote", "button", "select", "sensor", "switch"]Async HTTP client using aiohttp. Single class BraviaClient with:
Constructor: BraviaClient(host, psk, session)
Core method: async _request(service, method, params, version) — JSON-RPC call
Service wrappers organized by group:
| Group | Methods |
|---|---|
| System | get_system_info(), get_power_status(), set_power_status(on), get_wol_mode(), set_wol_mode(on), get_led_status(), set_led_status(on), get_remote_controller_info(), get_supported_api_info(), get_power_saving_mode(), set_power_saving_mode(mode), request_reboot(), get_current_time(), get_network_settings(), get_interface_info() |
| Audio | get_volume_info(), set_volume(level, target), set_mute(on), get_speaker_settings(), set_sound_settings(settings) |
| AV Content | get_playing_content(), set_play_content(uri), get_external_inputs(), get_scheme_list(), get_source_list(scheme), get_content_list(uri, start, count) |
| App Control | get_app_list(), set_active_app(uri), get_app_status_list(), terminate_apps() |
| Video | get_picture_quality(), set_picture_quality(settings), get_screen_rotation(), set_screen_rotation(angle) |
| IRCC | send_ircc(code) — SOAP request |
Error handling:
BraviaError— base exceptionBraviaConnectionError— TV unreachable (standby/suspend)BraviaAuthError— invalid PSKBraviaTurnedOffError— TV powered off (error code 7)- Timeout: 5 seconds for all requests
Step 1: User Input
- Fields: host (IP address), psk (Pre-Shared Key)
- Validate by calling
get_system_info()andget_power_status() - On success: auto-discover model name and serial
- Use serial number as unique_id to prevent duplicate entries
Error handling:
- Connection timeout → "Cannot connect"
- Auth error → "Invalid PSK"
- TV must be ON during setup
Update interval: 15 seconds
Data fetched per poll:
get_power_status()— always (lightweight)- If TV is active:
get_volume_info()— volume + mute stateget_playing_content()— current source/appget_external_inputs()— connected inputsget_app_status_list()— keyboard/cursor state
Stored data structure:
@dataclass
class BraviaState:
power: str # "active" | "standby"
volume: int | None
is_muted: bool | None
max_volume: int
playing_content: dict | None # uri, title, source
external_inputs: list[dict]
app_status: list[dict]Error handling in coordinator:
ConnectionError/ timeout → set power to "off", mark unavailable- Successive failures → exponential backoff (built into HA coordinator)
class BraviaEntity(CoordinatorEntity):
_attr_has_entity_name = True
def __init__(self, coordinator, entry):
super().__init__(coordinator)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, entry.unique_id)},
name=entry.data.get("model", "Sony Bravia Pro"),
manufacturer="Sony",
model=entry.data.get("model"),
sw_version=entry.data.get("firmware"),
)All entities inherit from this, sharing a single HA device.
Features:
TURN_ON,TURN_OFF— via setPowerStatus + WoLVOLUME_SET,VOLUME_STEP,VOLUME_MUTE— via audio serviceSELECT_SOURCE— via setPlayContent (HDMI inputs) + setActiveApp (apps)PLAY_MEDIA— via setActiveApp for app URIsBROWSE_MEDIA— browse apps and inputs
State mapping:
| TV State | HA State |
|---|---|
| active + playing | STATE_ON |
| active + idle | STATE_IDLE |
| standby | STATE_OFF |
| unreachable | STATE_OFF |
Source list: Combines external inputs (HDMI 1, HDMI 2, etc.) + installed apps (Netflix, YouTube, etc.)
Attributes:
media_title— from getPlayingContentInfosource— current input label or app namesource_list— merged inputs + apps
Features:
TURN_ON,TURN_OFF- Send command via
remote.send_commandservice
Implementation:
- On setup: fetch IRCC codes via
getRemoteControllerInfo, cache them send_command(command): look up command name → IRCC code → send SOAP- Support both command names ("VolumeUp") and raw IRCC codes
Activity list: Not applicable, but expose available commands as attribute.
| Button | API Call | Icon |
|---|---|---|
| Reboot | system.requestReboot | mdi:restart |
| Terminate Apps | appControl.terminateApps | mdi:close-box-multiple |
| Picture Off | system.setPowerSavingMode("pictureOff") | mdi:monitor-off |
| Picture On | system.setPowerSavingMode("off") | mdi:monitor |
| Select | API | Options |
|---|---|---|
| Picture Mode | video.setPictureQualitySettings | Discovered from TV |
| Sound Output | audio.setSoundSettings | speaker, hdmi, speaker_hdmi, audioSystem |
| Screen Rotation | video.setScreenRotation | 0, 90, 180, 270 |
| Sensor | API | Value |
|---|---|---|
| Model | system.getSystemInformation | e.g., "FW-75BZ35L" |
| Firmware | system.getSystemInformation | Firmware version |
| Serial | system.getSystemInformation | Serial number |
| MAC Address | system.getSystemInformation | MAC address |
These are fetched once during setup and stored in config entry data. They're diagnostic sensors.
| Switch | API Get/Set | Description |
|---|---|---|
| LED Indicator | getLEDIndicatorStatus / setLEDIndicatorStatus | Control front LED |
| Wake-on-LAN | getWolMode / setWolMode | Enable/disable WoL |
-
Phase 1 — Foundation
const.py— constantsbravia_client.py— full API clientmanifest.json— integration metadata
-
Phase 2 — Config & Coordinator
config_flow.py— setup wizardstrings.json— UI stringscoordinator.py— state pollingentity.py— base entity__init__.py— integration setup
-
Phase 3 — Entities
media_player.py— primary entityremote.py— IRCC commandsbutton.py— action buttonsselect.py— settings selectorssensor.py— system infoswitch.py— toggles
-
Phase 4 — Polish
services.yaml— custom servicestranslations/es.json— Spanish translation
- Single coordinator — One polling loop for all entities, reduces API calls
- Runtime discovery — Use
getSupportedApiInfoto discover available features per TV - Graceful degradation — If TV is off/unreachable, entities go unavailable without errors
- Source merging — External inputs + apps in one source list for media_player
- IRCC cache — Fetch remote codes once, cache for session lifetime
- No PyPI dependency — Client code lives inside the integration (custom component)
- WoL for power on — Store MAC address, send magic packet when user turns on TV from standby