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).