# Bravia

An Elixir client for the local REST API on Sony BRAVIA televisions. Control picture
settings, power, inputs, and audio over your network, with no cloud service involved.

```elixir
{:ok, tv} = Bravia.new("192.168.1.20", psk: "0000")

{:ok, 15} = Bravia.Video.get_brightness(tv)
:ok = Bravia.Video.set_brightness(tv, 8)
```

## Installation

```elixir
def deps do
  [{:bravia, "~> 0.1"}]
end
```

## Enabling control on your television

**Reading requires no setup.** You can query a BRAVIA on your network immediately.

**Changing anything requires a Pre-Shared Key**, which you set on the television:

> Settings → Network → Home Network → IP Control → Authentication →
> **Normal and Pre-Shared Key**

Choose a key there and pass it as `:psk`. Without one, writes fail with
`%Bravia.Error{reason: :unauthorized}` — by far the most common first-run problem.

## Finding a television

```elixir
{:ok, [device | _]} = Bravia.Discovery.discover()

device.friendly_name  #=> "SONY XR-42A90K"
device.model          #=> "XR-42A90K"
device.host           #=> "192.168.1.20"

{:ok, tv} = Bravia.from_device(device, psk: "0000")
```

Discovery uses SSDP and follows whatever URL the television advertises, which is not
a URL you can reliably construct yourself.

## What you can control

```elixir
# Picture
{:ok, targets} = Bravia.Video.get_picture_quality_settings(tv)
:ok = Bravia.Video.set_brightness(tv, 8)
:ok = Bravia.Video.set_picture_mode(tv, :game)

# Power
{:ok, :active} = Bravia.System.get_power_status(tv)
:ok = Bravia.System.set_power_saving_mode(tv, :picture_off)

# Inputs
{:ok, inputs} = Bravia.AvContent.get_current_external_inputs_status(tv)
:ok = Bravia.AvContent.set_play_content(tv, "extInput:hdmi?port=4")

# Audio
{:ok, outputs} = Bravia.Audio.get_volume_information(tv)
:ok = Bravia.Audio.set_volume(tv, 20)
:ok = Bravia.Audio.set_volume(tv, "+1")
```

Anything without a convenience function is still reachable:

```elixir
Bravia.RPC.call(tv, "video", "getPictureQualitySettings", [%{"target" => ""}], "1.0")
```

## Things the API will surprise you with

**Picture settings are stored per input and per picture mode.** A brightness change
lands in whichever slot is active and stays there. Nothing puts it back — read the
current value first if you intend to restore it.

**Ranges are model-specific.** This library does not hardcode them. Ask the set:

```elixir
{:ok, [target]} = Bravia.Video.get_picture_quality_settings(tv, "brightness")
target["candidate"]  #=> [%{"min" => 0, "max" => 50, "step" => 1}]
```

**Method versions differ between models.** The same television may offer
`setAudioVolume` at both `"1.0"` and `"1.2"`. Check before assuming:

```elixir
{:ok, capabilities} = Bravia.capabilities(tv)
capabilities["audio"]["setAudioVolume"]  #=> ["1.0", "1.2"]
```

**On OLED sets there is no backlight control.** `brightness` is the panel luminance.

## Scope

This is a stateless protocol client. It starts no processes and holds nothing between
calls, so polling, caching, and supervision stay in your application where they
belong.

Not included: IRCC remote key codes (a separate SOAP endpoint), app launching, and
the cookie/PIN pairing flow. Pre-Shared Key authentication only.

## Trying it against a real television

`bin/bravia` drives a set from the command line, so you can check behaviour without
writing any Elixir. It finds the television over SSDP, or set `BRAVIA_HOST` to skip
the search. Writes need `BRAVIA_PSK`.

```
$ bin/bravia status
power          active
saving         off
brightness     15
contrast       90
picture        game
input          HDMI 4 (PC)
volume         speaker 0/100

$ bin/bravia inputs
  HDMI 1 (Game)              extInput:hdmi?port=1
  HDMI 2 (Mister)            extInput:hdmi?port=2
  HDMI 3 (eARC/ARC) (Switch) extInput:hdmi?port=3
* HDMI 4 (PC)                extInput:hdmi?port=4
  AV                         extInput:composite?port=1

$ bin/bravia settings brightness
brightness             15         0..50

$ BRAVIA_PSK=0000 bin/bravia brightness 8
ok brightness -> 8
```

`bin/bravia help` lists every command, including a `call` escape hatch for methods
with no convenience function. For an interactive session, `just console` opens IEx
with `tv` bound to a client.

## Testing

The suite runs against responses recorded from a real Sony XR-42A90K, so no hardware
is needed:

```
just test          # or: mix test
```

To include the tests that talk to a real television:

```
BRAVIA_HOST=192.168.1.20 just probe
```

Add `BRAVIA_PSK` to include the write test, which changes a picture setting and
restores it afterwards. Without a key it is skipped rather than failed.

Re-record the fixtures with `BRAVIA_HOST=... just capture`.

## Development

`just` lists everything; the ones you will reach for:

| Recipe | What it does |
|---|---|
| `just test` | offline suite against fixtures |
| `just probe` | suite including live hardware tests |
| `just check` | full gate: compile `-Werror`, format, credo, dialyzer, test |
| `just look` | snapshot of the television's current state |
| `just discover` | find Sony televisions on the network |
| `just console` | IEx shell with `tv` bound |
| `just capture` | re-record fixtures from a live set |
| `just document` | build HTML docs |
| `just package` | build the Hex tarball without publishing |

## Licence

MIT.
