View Source 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.2"}
])

# we define a couple of aliases to not have to type that much
alias Fledex.LedAnimationManager
alias Fledex.Leds
alias Fledex.LedsDriver
alias Fledex.LedStripDriver.KinoDriver
alias Fledex.Color.Correction

{:ok, pid} = LedAnimationManager.start_link()

:ok

Preparation

To start with, we define a couple of aliases to make our life easier and more importantly, we start our LedAnimationManager. We will use it to create animations. The animations are actually maintained by the LedAnimator, but we will interact with it only through the LedAnimationManager.

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 LedsDriver) 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.func(: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 LedAnimationManager. Before we can define our animations we first have to define (and configure) our led strip. The LedAnimationManager 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.

LedAnimationManager.register_strip(:john, :kino)

LedAnimationManager.register_animations(:john, %{
  caine: %{
    def_func: &HelperFunctions.rainbow/1,
    send_config_func: &HelperFunctions.send_config/1
  }
})

Replacing our animation

We can now redefine our animation by regisetering different functions. Here we define a 3 led wide red dot that moves slowly from right to left.

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] >>> 5, 50)
    %{offset: slow_offset}
  end
end

LedAnimationManager.register_animations(:john, %{
  caine: %{
    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] >>> 2, 50)
    %{offset: faster_offset}
  end
end

LedAnimationManager.register_animations(:john, %{
  caine: %{
    def_func: &HelperFunctions2.red_leds/1,
    send_config_func: &HelperFunctions2.send_config/1
  },
  doe: %{
    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.

LedAnimationManager.register_animations(:john, %{
  caine: %{
    def_func: &HelperFunctions2.red_leds/1,
    send_config_func: &HelperFunctions2.send_config/1
  },
  doe: %{
    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] >>> 1, 50)
      %{offset: faster_offset}
    end
  }
})

From now on we will only define our definitions through the use of anonymous functions.