# API 1.17

espex 0.11.0 advertises API 1.17 and later. This page covers what 1.17
added.

## What's new

API 1.17 changes the serial proxy in two ways:

* **One owner per port.** The connection that sent `SUBSCRIBE` for a
  serial instance owns it. Other connections cannot configure, write to
  or reset it until the owner unsubscribes or disconnects.
* **Port mode.** `SerialProxySetModeRequest` switches a port between
  `RAW` (bytes pass through) and `PROTOCOL` (the device runs a protocol
  handler on the port), acknowledged with `SET_MODE`.

## What espex implements

### Ownership

espex records one owner per instance, across connections. A
client must `SUBSCRIBE` before anything else:

| Request from a non-owner | Result |
|--------------------------|--------|
| CONFIGURE, SET_MODEM_PINS, FLUSH, SET_MODE | `PORT_IN_USE`, adapter not called |
| WRITE | dropped (WRITE has no acknowledgement) |
| GET_MODEM_PINS | answered: it is read-only |
| SUBSCRIBE while another live connection owns it | `PORT_IN_USE` |
| UNSUBSCRIBE | `OK` |

`UNSUBSCRIBE` and a disconnect both close the owner's handle
(`c:Espex.SerialProxy.close/1`) before releasing the instance, so a free
instance really is free. Only the owner receives
`SerialProxyDataReceived`. See "Ownership" in `Espex.SerialProxy`.

### Port mode

The optional `c:Espex.SerialProxy.set_mode/2` is the whole surface:

| Mode | Adapter exports `set_mode/2` | Adapter does not |
|------|------------------------------|------------------|
| `RAW` | its answer | `OK` |
| `PROTOCOL` | its answer | `NOT_SUPPORTED` |

A mode outside the enum is `INVALID_ARGUMENT`. espex reapplies
`PROTOCOL` after a `CONFIGURE` reopens the port, and the mode ends with
the handle: `c:Espex.SerialProxy.close/1` is where the adapter tears its
protocol handler down. See "Port mode" in `Espex.SerialProxy`.

## Usage

    defmodule MyApp.SerialPorts do
      @behaviour Espex.SerialProxy

      # ... list_instances/0, open/3, write/2, close/1 as before ...

      @impl true
      def set_mode(handle, :protocol), do: MyApp.Tap.start(handle)
      def set_mode(handle, :raw), do: MyApp.Tap.stop(handle)

      @impl true
      def close(handle) do
        MyApp.Tap.stop(handle)
        MyApp.UART.close(handle)
      end
    end

## Upgrade notes

* A client that writes without subscribing first now gets
  `PORT_IN_USE`, or its bytes are dropped. Home Assistant subscribes
  first.
* espex still opens a handle per connection, so a non-owner's
  `GET_MODEM_PINS` may open a second handle on an instance. Whether
  that is allowed stays the adapter's decision.
* A handle must not outlive the `subscriber` pid it was opened for: a
  connection killed outright cannot close it, though its ownership is
  still released.
