3. Fledex: Animations
# In this example we'll use the simple setup (experiments are inteded to not run on real
# hardware but on a normal livebook, This makes it easier to stay up-to-date and experiment
# We start to define our fledex library, directly linking to the latest version on github
Mix.install([
{:fledex, "~>0.5"}
])
# we define a couple of aliases to not have to type that much
alias Fledex.Animation.Manager
alias Fledex.Leds
alias Fledex.LedStrip
alias Fledex.Driver.Impl.Kino
alias Fledex.Color.Correction
{:ok, pid} = Manager.start_link()
:ok
Preparation
To start with, we define a couple of aliases to make our life easier and more importantly, we start our Fledex.Animation.Manager
. We will use it to create animations. The animations are actually maintained by the Fledex.Animation.Animator
, but we will interact with it only through the Fledex.Animation.Manager
.
Helper functions
We define a bit of helper functions that we will use when we define our animations. Those helper functions are the actual definitions of how the ledstrip will look.
Note that the functions get in a map (here called triggers) that can contain serveral
triggers. It will definitely contain one trigger (caused by the Fledex.LedStrip
) with
the name of the led strip. The structure does not need to be used, but can be used in
order to change the animation based on the trigger. Here we use the trigger in the send
configuration to specify an offset to the leds and thereby create a movement.
defmodule HelperFunctions do
@rainbow Leds.leds(50)
|> Leds.rainbow(%{
num_leds: 50,
reversed: true
})
def rainbow(_triggers) do
@rainbow
end
def send_config(triggers) do
%{offset: triggers[:john]}
end
end
Creating a first animation
Now it’s time to define our first animation by using the Fledex.Animation.Manager
. Before we can define our animations we first have to define (and configure) our led strip. The Fledex.Animation.Manager
can manage serveral strips at the same time.
We call our led strip :john
(it has to be an atom). In this example we will only use the simple kino driver (and configure it with defaults) and thereby display the result within our livebook.
We then define an animation configuration that consists of a map with they key being the name of the animation and the value being the configuration of that animation. Each animation configuration needs to specify the :type
as :animation
, because (as we will see later) other types exist, and a :def_func
that defines our animation (potentially with an additional :send_config_func
as shown here. We’ll look at this in more detail in a later section).
Manager.register_strip(:john, [{Kino, []}], [])
Manager.register_config(:john, %{
caine: %{
type: :animation,
def_func: &HelperFunctions.rainbow/1,
send_config_func: &HelperFunctions.send_config/1
}
})
Replacing our animation
We can now redefine our animation by registering different functions. Here we define a 3 led wide red dot that moves slowly from right to left.
Note: we are starting to use color names instead of defining it through an integer. If you want to learn more, then take a small detour to the Color livebook
import Bitwise
defmodule HelperFunctions2 do
@red_leds Leds.leds(50) |> Leds.light(:red) |> Leds.light(:red) |> Leds.light(:red)
def red_leds(_triggers) do
@red_leds
end
def send_config(triggers) do
slow_offset = rem(triggers[:john] || 0 >>> 5, 50)
%{offset: slow_offset}
end
end
Manager.register_config(:john, %{
caine: %{
type: :animation,
def_func: &HelperFunctions2.red_leds/1,
send_config_func: &HelperFunctions2.send_config/1
}
})
Defining serveral animations
It’s also possible to define serveral animations. Here we continue with our previous animation (we do have to explicitly specify it again, otherwise it would be removed) and add a new animation with 3 leds that move faster from right to left.
import Bitwise
defmodule HelperFunctions3 do
@rgb_leds Leds.leds(50) |> Leds.light(:red) |> Leds.light(:green) |> Leds.light(:blue)
def rgb_leds(_triggers) do
@rgb_leds
end
def send_config(triggers) do
faster_offset = 50 - rem(triggers[:john] || 0 >>> 2, 50)
%{offset: faster_offset}
end
end
Manager.register_config(:john, %{
caine: %{
type: :animation,
def_func: &HelperFunctions2.red_leds/1,
send_config_func: &HelperFunctions2.send_config/1
},
doe: %{
type: :animation,
def_func: &HelperFunctions3.rgb_leds/1,
send_config_func: &HelperFunctions3.send_config/1
}
})
Use of anonymous functions
It is also possible to use an anonymous function during the definition which avoids to create a separate module. Here we redefine our above definition, but this time we define :doe
with an anonymous function. To really see that we have changed the definition we define the leds as all blue. In addition we change the send_config_func
to animate the dots even faster.
The advantage of using the anonymous functions is that you don’t get any compilation error, that occures if you recompile the module. Try it out by reevaluating the below definition several times and then doing the same thing with the previous definition.
This allows for a smooth replacement of definitions.
Manager.register_config(:john, %{
caine: %{
type: :animation,
def_func: &HelperFunctions2.red_leds/1,
send_config_func: &HelperFunctions2.send_config/1
},
doe: %{
type: :animation,
def_func: fn _triggers ->
Leds.leds(50) |> Leds.light(:blue) |> Leds.light(:blue) |> Leds.light(:blue)
end,
send_config_func: fn triggers ->
faster_offset = 50 - rem(triggers[:john] || 0 >>> 1, 50)
%{offset: faster_offset}
end
}
})
From now on we will only define our definitions through the use of anonymous functions.