README.md

# FritzApi

[![Build Status](https://github.com/adriankumpf/fritz-api/workflows/CI/badge.svg)](https://github.com/adriankumpf/fritz-api/actions)
[![Docs](https://img.shields.io/badge/hex-docs-green.svg?style=flat)](https://hexdocs.pm/fritz_api)
[![Hex.pm](https://img.shields.io/hexpm/v/fritz_api?color=%23714a94)](http://hex.pm/packages/fritz_api)

<!-- MDOC !-->

Fritz!Box Home Automation API Client for Elixir
([documentation](https://hexdocs.pm/fritz_api)).

## Usage

```elixir
iex> {:ok, client} = FritzApi.Client.new()
...>                 |> FritzApi.Client.login("admin", "changeme")

iex> FritzApi.get_device_list_infos(client)
{:ok, [%FritzApi.Actor{
  ain: "687690315761",
  fwversion: "03.87",
  id: 21,
  manufacturer: "AVM",
  name: "FRITZ!DECT #1",
  powermeter: %{energy: 0.475, power: 0.0},
  present: true,
  productname: "FRITZ!DECT 200",
  switch: %{
    devicelock: false,
    lock: false,
    mode: :manual,
    state: false
  },
  temperature: %{
    celsius: 23.5,
    offset: 0.0
  }
}]}

iex> FritzApi.set_switch_off(client, "687690315761")
:ok

iex> FritzApi.get_temperature(client, "687690315761")
{:ok, 23.5}
```

### Example: Automatic Session Refresh

```elixir
defmodule Switch do
  use GenServer

  defmodule State do
    @derive {Inspect, except: [:password]}
    defstruct [:username, :password, :ain, :client]
  end

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts)
  end

  def turn_on, do: GenServer.call(__MODULE__, :turn_on)
  def turn_off, do: GenServer.call(__MODULE__, :turn_off)

  @impl true
  def init(opts) do
    client =
      FritzApi.Client.new(
        base_url: opts[:host],
        receive_timeout: :timer.seconds(30)
      )

    state = %State{
      username: Keyword.fetch!(opts, :username),
      password: Keyword.fetch!(opts, :password),
      ain: Keyword.fetch!(opts, :ain),
      client: client
    }

    {:ok, state, {:continue, :login}}
  end

  @impl true
  def handle_continue(:login, %State{} = state) do
    {:ok, client} = FritzApi.Client.login(state.client, state.username, state.password)
    {:noreply, %State{state | client: client}}
  end

  @impl true
  def handle_call(:turn_on, _from, %State{} = state) do
    {result, state} = refresh_session(&FritzApi.set_switch_on(&1, state.ain), state)
    {:reply, result, state}
  end

  def handle_call(:turn_off, _from, %State{} = state) do
    {result, state} = refresh_session(&FritzApi.set_switch_off(&1, state.ain), state)
    {:reply, result, state}
  end


  defp refresh_session(fun, %State{} = state) do
    with {:error, %FritzApi.Error{reason: :session_expired}} <- fun.(state.client),
         {:ok, client} <- FritzApi.Client.login(state.client, state.username, state.password) do
      {fun.(client), %State{state | client: client}}
    else
      result -> {result, state}
    end
  end
end
```

```elixir
iex> {:ok, pid} = Switch.start_link(username: "admin", password: "admin", ain: "000111222333")
iex> Switch.turn_on(pid)
:ok
```

<!-- MDOC !-->

## Installation

Add `fritz_api` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:fritz_api, "~> 2.1"},
    {:hackney, "~> 1.17"}
  ]
end
```

By default, `fritz_api` uses [hackney](https://github.com/benoitc/hackney) (via `Tesla.Adapter.Hackney`). Add `hackney` to the list of dependencies too if you don't want to use another HTTP adapter (see [Tesla Adapters](https://github.com/teamon/tesla#adapters) to find all available adapters and [`FritzApi.Client.new/1`](https://hexdocs.pm/fritz_api/FritzApi.Client.html#new/1) on how to configure another adapter).

The docs can be found at [hexdocs.pm/fritz_api](https://hexdocs.pm/fritz_api).

## References

- [AHA HTTP Interface](https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/AHA-HTTP-Interface.pdf)
- [AVM Technical Note - Session ID](https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/AVM_Technical_Note_-_Session_ID.pdf)