Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Build the binary clock

4.5-Andrew's%20final%20livebook.livemd

Build the binary clock

Mix.install([
  {:circuits_spi, "~> 1.3"},
  {:kino, "~> 0.6.2"}
])

alias Circuits.SPI

import IEx.Helpers
IEx.Helpers

Open the bus

{:ok, spi} = Circuits.SPI.open("spidev0.0", mode: 3, lsb_first: true)
{:ok, #Reference<0.1522744948.1297219593.12443>}

Control one LED

SPI.transfer(spi, <<0b00000010>>)
SPI.transfer(spi, <<0x40>>)
SPI.transfer(spi, <<0xC0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0>>)
SPI.transfer(spi, <<0x88>>)
{:ok, <<255>>}

CRC (construct reduce convert)

defmodule Counter do
  def new(input) do
    %{count: String.to_integer(input)}
  end

  def add(%{count: acc}, value) do
    %{count: acc + value}
  end

  def show(%{count: count}), do: "The count is #{count}"
end
{:module, Counter, <<70, 79, 82, 49, 0, 0, 8, ...>>, {:show, 1}}
"42"
|> Counter.new()
|> Counter.add(1)
|> Counter.add(1)
|> Counter.add(-1)
|> Counter.show()
"The count is 43"

Build a clock in elixir

defmodule ClockCore do
  defstruct [:hour, :minute, :second]

  def new(initial_time) do
    %__MODULE__{hour: initial_time.hour, minute: initial_time.minute, second: initial_time.second}
  end

  def tick(%{hour: 23, minute: 59, second: 59}), do: %__MODULE__{hour: 0, minute: 0, second: 0}

  def tick(%{hour: hour, minute: 59, second: 59}),
    do: %__MODULE__{hour: hour + 1, minute: 0, second: 0}

  def tick(%{hour: hour, minute: minute, second: 59}),
    do: %__MODULE__{hour: hour, minute: minute + 1, second: 0}

  def tick(%{hour: hour, minute: minute, second: second}),
    do: %__MODULE__{hour: hour, minute: minute, second: second + 1}

  def show(clock, :string), do: inspect(clock)

  def show(%{hour: hour, minute: minute, second: second}, :binary) do
    (to_digits(hour) ++ to_digits(minute) ++ to_digits(second))
    |> Enum.flat_map(fn d ->
      [d, 0]
    end)
    |> :binary.list_to_bin()
  end

  defp to_digits(integer) when integer < 10 do
    [0] ++ Integer.digits(integer)
  end

  defp to_digits(integer) do
    Integer.digits(integer)
  end
end
{:module, ClockCore, <<70, 79, 82, 49, 0, 0, 14, ...>>, {:to_digits, 1}}
core =
  Time.utc_now()
  |> ClockCore.new()
  |> ClockCore.tick()
  |> ClockCore.tick()
  |> ClockCore.show(:binary)
<<2, 0, 2, 0, 2, 0, 1, 0, 5, 0, 3, 0>>

Boundary: adapter

defmodule Adapter.Dev do
  defstruct [:clock]

  def new(time) do
    %__MODULE__{clock: ClockCore.new(time)}
  end

  def tick(%{clock: clock}) do
    %__MODULE__{clock: ClockCore.tick(clock)}
  end

  def show(%{clock: clock}) do
    ClockCore.show(clock, :string)
  end
end
{:module, Adapter.Dev, <<70, 79, 82, 49, 0, 0, 10, ...>>, {:show, 1}}
defmodule Adapter.SPI do
  defstruct [:clock, :spi]

  def new(time) do
    {:ok, spi} = SPI.open("spidev0.0", mode: 3, lsb_first: true)
    SPI.transfer(spi, <<0b00000010>>)
    SPI.transfer(spi, <<0x40>>)
    SPI.transfer(spi, <<0x88>>)

    %__MODULE__{clock: ClockCore.new(time), spi: spi}
  end

  def tick(%{clock: clock, spi: spi}) do
    %__MODULE__{clock: ClockCore.tick(clock), spi: spi}
  end

  def show(%{clock: clock, spi: spi}) do
    SPI.transfer(spi, <<0xC0>> <> ClockCore.show(clock, :binary))
  end
end
{:module, Adapter.SPI, <<70, 79, 82, 49, 0, 0, 12, ...>>, {:show, 1}}
clock =
  Time.utc_now()
  |> Adapter.SPI.new()
%Adapter.SPI{
  clock: %ClockCore{hour: 22, minute: 29, second: 46},
  spi: #Reference<0.1522744948.1297219593.13760>
}
Kino.animate(1_000, clock, fn c ->
  next_clock = Adapter.SPI.tick(c)
  {:cont, Adapter.SPI.show(next_clock), next_clock}
end)

22:33:11.745 [debug] wpa_supplicant: wlan0: WPA: Group rekeying completed with 34:36:3b:c0:78:38 [GTK=CCMP]

22:33:11.746 [debug] wpa_supplicant(wlan0): WPA: Group rekeying completed with 34:36:3b:c0:78:38 [GTK=CCMP]
Nerves.

                                   Supervisor                                   

A behaviour module for implementing supervisors.

A supervisor is a process which supervises other processes, which we refer to
as child processes. Supervisors are used to build a hierarchical process
structure called a supervision tree. Supervision trees provide fault-tolerance
and encapsulate how our applications start and shutdown.

A supervisor may be started directly with a list of children via start_link/2
or you may define a module-based supervisor that implements the required
callbacks. The sections below use start_link/2 to start supervisors in most
examples, but it also includes a specific section on module-based ones.

## Examples

In order to start a supervisor, we need to first define a child process that
will be supervised. As an example, we will define a GenServer that represents a
stack:

    defmodule Stack do
      use GenServer
    
      def start_link(state) do
        GenServer.start_link(__MODULE__, state, name: __MODULE__)
      end
    
      ## Callbacks
    
      @impl true
      def init(stack) do
        {:ok, stack}
      end
    
      @impl true
      def handle_call(:pop, _from, [head | tail]) do
        {:reply, head, tail}
      end
    
      @impl true
      def handle_cast({:push, head}, tail) do
        {:noreply, [head | tail]}
      end
    end

The stack is a small wrapper around lists. It allows us to put an element on
the top of the stack, by prepending to the list, and to get the top of the
stack by pattern matching.

We can now start a supervisor that will start and supervise our stack process.
The first step is to define a list of child specifications that control how
each child behaves. Each child specification is a map, as shown below:

    children = [
      # The Stack is a child started via Stack.start_link([:hello])
      %{
        id: Stack,
        start: {Stack, :start_link, [[:hello]]}
      }
    ]
    
    # Now we start the supervisor with the children and a strategy
    {:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
    
    # After started, we can query the supervisor for information
    Supervisor.count_children(pid)
    #=> %{active: 1, specs: 1, supervisors: 0, workers: 1}

Note that when starting the GenServer, we are registering it with name Stack,
which allows us to call it directly and get what is on the stack:

    GenServer.call(Stack, :pop)
    #=> :hello
    
    GenServer.cast(Stack, {:push, :world})
    #=> :ok
    
    GenServer.call(Stack, :pop)
    #=> :world

However, there is a bug in our stack server. If we call :pop and the stack is
empty, it is going to crash because no clause matches:

    GenServer.call(Stack, :pop)
    ** (exit) exited in: GenServer.call(Stack, :pop, 5000)

Luckily, since the server is being supervised by a supervisor, the supervisor
will automatically start a new one, with the initial stack of [:hello]:

    GenServer.call(Stack, :pop)
    #=> :hello

Supervisors support different strategies; in the example above, we have chosen
:one_for_one. Furthermore, each supervisor can have many workers and/or
supervisors as children, with each one having its own configuration (as
outlined in the "Child specification" section).

The rest of this document will cover how child processes are specified, how
they can be started and stopped, different supervision strategies and more.

## Child specification

The child specification describes how the supervisor starts, shuts down, and
restarts child processes.

The child specification is a map containing up to 6 elements. The first two
keys in the following list are required, and the remaining ones are optional:

  • :id - any term used to identify the child specification internally by
    the supervisor; defaults to the given module. In the case of conflicting
    :id values, the supervisor will refuse to initialize and require explicit
    IDs. This key is required.
  • :start - a tuple with the module-function-args to be invoked to start
    the child process. This key is required.
  • :restart - an atom that defines when a terminated child process should
    be restarted (see the "Restart values" section below). This key is optional
    and defaults to :permanent.
  • :shutdown - an integer or atom that defines how a child process should
    be terminated (see the "Shutdown values" section below). This key is
    optional and defaults to 5_000 if the type is :worker or :infinity if the
    type is :supervisor.
  • :type - specifies that the child process is a :worker or a :supervisor.
    This key is optional and defaults to :worker.

There is a sixth key, :modules, which is optional and is rarely changed. It is
set automatically based on the :start value.

Let's understand what the :shutdown and :restart options control.

### Shutdown values (:shutdown)

The following shutdown values are supported in the :shutdown option:

  • :brutal_kill - the child process is unconditionally and immediately
    terminated using Process.exit(child, :kill).
  • any integer >= 0 - the amount of time in milliseconds that the
    supervisor will wait for its children to terminate after emitting a
    Process.exit(child, :shutdown) signal. If the child process is not trapping
    exits, the initial :shutdown signal will terminate the child process
    immediately. If the child process is trapping exits, it has the given
    amount of time to terminate. If it doesn't terminate within the specified
    time, the child process is unconditionally terminated by the supervisor via
    Process.exit(child, :kill).
  • :infinity - works as an integer except the supervisor will wait
    indefinitely for the child to terminate. If the child process is a
    supervisor, the recommended value is :infinity to give the supervisor and
    its children enough time to shut down. This option can be used with regular
    workers but doing so is discouraged and requires extreme care. If not used
    carefully, the child process will never terminate, preventing your
    application from terminating as well.

### Restart values (:restart)

The :restart option controls what the supervisor should consider to be a
successful termination or not. If the termination is successful, the supervisor
won't restart the child. If the child process crashed, the supervisor will
start a new one.

The following restart values are supported in the :restart option:

  • :permanent - the child process is always restarted.
  • :temporary - the child process is never restarted, regardless of the
    supervision strategy: any termination (even abnormal) is considered
    successful.
  • :transient - the child process is restarted only if it terminates
    abnormally, i.e., with an exit reason other than :normal, :shutdown, or
    {:shutdown, term}.

For a more complete understanding of the exit reasons and their impact, see the
"Exit reasons and restarts" section.

## child_spec/1

When starting a supervisor, we pass a list of child specifications. Those
specifications are maps that tell how the supervisor should start, stop and
restart each of its children:

    %{
      id: Stack,
      start: {Stack, :start_link, [[:hello]]}
    }

The map above defines a child with :id of Stack that is started by calling
Stack.start_link([:hello]).

However, specifying the child specification for each child as a map can be
quite error prone, as we may change the Stack implementation and forget to
update its specification. That's why Elixir allows you to pass a tuple with the
module name and the start_link argument instead of the specification:

    children = [
      {Stack, [:hello]}
    ]

The supervisor will then invoke Stack.child_spec([:hello]) to retrieve a child
specification. Now the Stack module is responsible for building its own
specification, for example, we could write:

    def child_spec(arg) do
      %{
        id: Stack,
        start: {Stack, :start_link, [arg]}
      }
    end

Luckily for us, use GenServer already defines a Stack.child_spec/1 exactly like
above. If you need to customize the GenServer, you can pass the options
directly to use GenServer:

    use GenServer, restart: :transient

Finally, note it is also possible to simply pass the Stack module as a child:

    children = [
      Stack
    ]

When only the module name is given, it is equivalent to {Stack, []}. By
replacing the map specification by {Stack, [:hello]} or Stack, we keep the
child specification encapsulated in the Stack module, using the default
implementation defined by use GenServer. We can now share our Stack worker with
other developers and they can add it directly to their supervision tree without
worrying about the low-level details of the worker.

Overall, the child specification can be one of the following:

  • a map representing the child specification itself - as outlined in the
    "Child specification" section
  • a tuple with a module as first element and the start argument as second
    - such as {Stack, [:hello]}. In this case, Stack.child_spec([:hello]) is
    called to retrieve the child specification
  • a module - such as Stack. In this case, Stack.child_spec([]) is called
    to retrieve the child specification

If you need to convert a tuple or a module child specification to a map or
modify a child specification, you can use the Supervisor.child_spec/2 function.
For example, to run the stack with a different :id and a :shutdown value of 10
seconds (10_000 milliseconds):

    children = [
      Supervisor.child_spec({Stack, [:hello]}, id: MyStack, shutdown: 10_000)
    ]

## Module-based supervisors

In the example above, a supervisor was started by passing the supervision
structure to start_link/2. However, supervisors can also be created by
explicitly defining a supervision module:

    defmodule MyApp.Supervisor do
      # Automatically defines child_spec/1
      use Supervisor
    
      def start_link(init_arg) do
        Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
      end
    
      @impl true
      def init(_init_arg) do
        children = [
          {Stack, [:hello]}
        ]
    
        Supervisor.init(children, strategy: :one_for_one)
      end
    end

The difference between the two approaches is that a module-based supervisor
gives you more direct control over how the supervisor is initialized. Instead
of calling Supervisor.start_link/2 with a list of children that are
automatically initialized, we manually initialize the children by calling
Supervisor.init/2 inside its c:init/1 callback.

use Supervisor also defines a child_spec/1 function which allows us to run
MyApp.Supervisor as a child of another supervisor or at the top of your
supervision tree as:

    children = [
      MyApp.Supervisor
    ]
    
    Supervisor.start_link(children, strategy: :one_for_one)

A general guideline is to use the supervisor without a callback module only at
the top of your supervision tree, generally in the c:Application.start/2
callback. We recommend using module-based supervisors for any other supervisor
in your application, so they can run as a child of another supervisor in the
tree. The child_spec/1 generated automatically by Supervisor can be customized
with the following options:

  • :id - the child specification identifier, defaults to the current
    module
  • :restart - when the supervisor should be restarted, defaults to
    :permanent

The @doc annotation immediately preceding use Supervisor will be attached to
the generated child_spec/1 function.

## `start_link/2`, `init/2`, and strategies

So far we have started the supervisor passing a single child as a tuple as well
as a strategy called :one_for_one:

    children = [
      {Stack, [:hello]}
    ]
    
    Supervisor.start_link(children, strategy: :one_for_one)

or from inside the c:init/1 callback:

    children = [
      {Stack, [:hello]}
    ]
    
    Supervisor.init(children, strategy: :one_for_one)

The first argument given to start_link/2 and init/2 is a list of child
specifications as defined in the "child_spec/1" section above.

The second argument is a keyword list of options:

  • :strategy - the supervision strategy option. It can be either
    :one_for_one, :rest_for_one or :one_for_all. Required. See the "Strategies"
    section.
  • :max_restarts - the maximum number of restarts allowed in a time frame.
    Defaults to 3.
  • :max_seconds - the time frame in which :max_restarts applies. Defaults
    to 5.
  • :name - a name to register the supervisor process. Supported values are
    explained in the "Name registration" section in the documentation for
    GenServer. Optional.

### Strategies

Supervisors support different supervision strategies (through the :strategy
option, as seen above):

  • :one_for_one - if a child process terminates, only that process is
    restarted.
  • :one_for_all - if a child process terminates, all other child processes
    are terminated and then all child processes (including the terminated one)
    are restarted.
  • :rest_for_one - if a child process terminates, the terminated child
    process and the rest of the children started after it, are terminated and
    restarted.

In the above, process termination refers to unsuccessful termination, which is
determined by the :restart option.

To dynamically supervise children, see DynamicSupervisor.

### Name registration

A supervisor is bound to the same name registration rules as a GenServer. Read
more about these rules in the documentation for GenServer.

## Start and shutdown

When the supervisor starts, it traverses all child specifications and then
starts each child in the order they are defined. This is done by calling the
function defined under the :start key in the child specification and typically
defaults to start_link/1.

The start_link/1 (or a custom) is then called for each child process. The
start_link/1 function must return {:ok, pid} where pid is the process
identifier of a new process that is linked to the supervisor. The child process
usually starts its work by executing the c:init/1 callback. Generally speaking,
the init callback is where we initialize and configure the child process.

The shutdown process happens in reverse order.

When a supervisor shuts down, it terminates all children in the opposite order
they are listed. The termination happens by sending a shutdown exit signal, via
Process.exit(child_pid, :shutdown), to the child process and then awaiting for
a time interval for the child process to terminate. This interval defaults to
5000 milliseconds. If the child process does not terminate in this interval,
the supervisor abruptly terminates the child with reason :kill. The shutdown
time can be configured in the child specification which is fully detailed in
the next section.

If the child process is not trapping exits, it will shutdown immediately when
it receives the first exit signal. If the child process is trapping exits, then
the terminate callback is invoked, and the child process must terminate in a
reasonable time interval before being abruptly terminated by the supervisor.

In other words, if it is important that a process cleans after itself when your
application or the supervision tree is shutting down, then this process must
trap exits and its child specification should specify the proper :shutdown
value, ensuring it terminates within a reasonable interval.

## Exit reasons and restarts

A supervisor restarts a child process depending on its :restart configuration.
For example, when :restart is set to :transient, the supervisor does not
restart the child in case it exits with reason :normal, :shutdown or
{:shutdown, term}.

So one may ask: which exit reason should I choose when exiting? There are three
options:

  • :normal - in such cases, the exit won't be logged, there is no restart
    in transient mode, and linked processes do not exit
  • :shutdown or {:shutdown, term} - in such cases, the exit won't be
    logged, there is no restart in transient mode, and linked processes exit
    with the same reason unless they're trapping exits
  • any other term - in such cases, the exit will be logged, there are
    restarts in transient mode, and linked processes exit with the same reason
    unless they're trapping exits

Note that the supervisor that reaches maximum restart intensity will exit with
:shutdown reason. In this case the supervisor will only be restarted if its
child specification was defined with the :restart option set to :permanent (the
default).