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

2. Fledex: Some simple tests

livebooks/2_fledex_first_steps.livemd

2. Fledex: Some simple tests

# We start to define some libraries we need. If you are running on a laptop
# the libraries will be loaded, but on a nerves_livebook you can only use
# what has been compiled in.
Mix.install([
  {:fledex, "~>0.5"}
])

# we define a couple of aliases to not have to type that much
alias Fledex.Leds
alias Fledex.LedStrip
alias Fledex.Driver.Impl.Kino
alias Fledex.Driver.Impl.Spi
alias Fledex.Color.Correction

:ok

Defining a strip

Fledex has several drivers which can also be used in parallel. It’s probably best to only use a single driver to start with. The Kino driver does not have any hardware dependency and therefore is probably a good start to ensure that the library is loaded correctly.

At this point, we won’t go into the details what all the settings really mean, so just accept them for what they are. What is important to understand is the first parameter (LedStrip) which is the name of our strip through which we will refer to it. For simplicity we use the same name as the name of the module, but it can be any atom.

This allows us to have several strips at the same time. Whenever we interact with the LedStrip we need to use that name to specify which strip we mean.

# frame = Kino.Frame.new() |> Kino.render()

{:ok, pid} =
  LedStrip.start_link(
    LedStrip,
    [
      {Kino, update_freq: 1, color_correction: Correction.no_color_correction()}
      # {Spi, color_correction: Correction.define_correction(
      #   Correction.Color.typical_smd5050(),
      #   Correction.Temperature.uncorrected_temperature()
      # )}
    ],
    timer_only_dirty_update: false,
    merge_strategy: :cap
  )

Setting individual LEDs

Now we are ready to send some data to the led_strip. Before we send some color information, we first define a namspace (:default) to which we will do the drawing. Each led strip can have several namespaces that can operate independently and that get merged at the end to the same led strip (similar to a window in a window server). We’ll see later why this is useful.

LedStrip.define_namespace(LedStrip, :default)
LedStrip.set_leds(LedStrip, :default, [0xFF0000, 0x00FF00, 0x0000FF])

Helper functions

We now define a couple of helper functions. The first 2 functions (red/1 and blue/1) are talking directly with the LED driver, the latter two (rainbow/1 and gradient/1) are making use of the Leds client module that helps to create nice effects like a rainbow.

All functions are trying to create a simple animation by configuring the leds, sleeping for a while, and then reconfiguring the leds.

defmodule Helpers do
  def red(namespace) do
    Enum.each(1..10, fn _index ->
      LedStrip.set_leds(LedStrip, namespace, [0xFF0000, 0x000000, 0x000000, 0x000000, 0x000000])
      Process.sleep(600)
      LedStrip.set_leds(LedStrip, namespace, [0x000000, 0xFF0000, 0x000000, 0x000000, 0x000000])
      Process.sleep(600)
      LedStrip.set_leds(LedStrip, namespace, [0x000000, 0x000000, 0xFF0000, 0x000000, 0x000000])
      Process.sleep(600)
      LedStrip.set_leds(LedStrip, namespace, [0x000000, 0x000000, 0x000000, 0xFF0000, 0x000000])
      Process.sleep(600)
      LedStrip.set_leds(LedStrip, namespace, [0x000000, 0x000000, 0x000000, 0x000000, 0xFF0000])
      Process.sleep(600)
    end)
  end

  def blue(namespace) do
    Enum.each(1..10, fn _index ->
      LedStrip.set_leds(LedStrip, namespace, [
        0x000000,
        0x000000,
        0x000000,
        0x000000,
        0x0000FF,
        0x00FF00
      ])

      Process.sleep(600)

      LedStrip.set_leds(LedStrip, namespace, [
        0x000000,
        0x000000,
        0x000000,
        0x0000FF,
        0x000000,
        0x000000
      ])

      Process.sleep(600)

      LedStrip.set_leds(LedStrip, namespace, [
        0x000000,
        0x000000,
        0x0000FF,
        0x000000,
        0x000000,
        0x00FF00
      ])

      Process.sleep(600)

      LedStrip.set_leds(LedStrip, namespace, [
        0x000000,
        0x0000FF,
        0x000000,
        0x000000,
        0x000000,
        0x000000
      ])

      Process.sleep(600)

      LedStrip.set_leds(LedStrip, namespace, [
        0x0000FF,
        0x000000,
        0x000000,
        0x000000,
        0x000000,
        0x00FF00
      ])

      Process.sleep(600)
    end)
  end

  def rainbow(namespace) do
    Enum.each(0..10000, fn index ->
      config = [
        num_leds: 50,
        reversed: true
      ]

      Leds.leds(50)
      |> Leds.set_led_strip_info(namespace, LedStrip)
      |> Leds.rainbow(config)
      |> Leds.send(offset: index)

      # before sending the next update we sleep a bit
      Process.sleep(100)
    end)
  end

  def gradient(namespace) do
    Enum.each(0..10000, fn index ->
      Leds.leds(50)
      |> Leds.set_led_strip_info(namespace, LedStrip)
      |> Leds.gradient(0xFF0000, 0x0000FF, %{num_leds: 50})
      |> Leds.send(
        offset: index,
        rotate_left: true
      )

      # before sending the next update we sleep a bit
      Process.sleep(100)
    end)
  end
end

Take note, that our functions create sequences of different length.

Red and blue

Now we can try it out with one of the first functions. Let’s call our red/1 function. We spawn it in a separate process. The reason for that you’ll see in a second.

Note: you might have to scroll up to see the animation

LedStrip.define_namespace(LedStrip, :red)

spawn(fn ->
  Helpers.red(:red)
end)

Similarly you can do the same thing with the blue/1 function. Where it gets interesting is, when you start first the red function and then start the blue function while the red function is still running.

Note: Don’t forget to use a differnt namespace name compared to the above function, otherwise one function would constantly overwrite what the other function has defined. We want them to live in parallel and get merged.

LedStrip.define_namespace(LedStrip, :blue)

spawn(fn ->
  Helpers.blue(:blue)
end)

While both functions are running, they will

Rainbow

Now we can use the above functions with our Strip. We can run the functions by spawning a new thread. This allows to also see what happens if another thread works in parallel. Here we create a rainbow effect that will be send with a changing offset so that the rainbow rotates through the LEDs

LedStrip.define_namespace(LedStrip, :rainbow)
spawn(fn -> Helpers.rainbow(:rainbow) end)

Gradient

Here we create a gradient between red and blue. Also this one we let rotate through. Note, depending on whether you started the rainbow above or not, you might see different results. Try this section with and without running the Rainbow section.

LedStrip.define_namespace(LedStrip, :gradient)
spawn(fn -> Helpers.gradient(:gradient) end)

We can drop the namespaces to see what happens

LedStrip.drop_namespace(LedStrip, :gradient)

As we can see our animation stops to be visible and our calls to set_leds will fail (but we swallow those errors).

Several animations

This test is running a couple of different ligth definitions in parallel.

LedStrip.define_namespace(LedStrip, :john)
LedStrip.define_namespace(LedStrip, :jane)
LedStrip.define_namespace(LedStrip, :default)

spawn(fn -> Helpers.blue(:jane) end)
spawn(fn -> Helpers.red(:john) end)
spawn(fn -> Helpers.rainbow(:default) end)

As you can see the animations overlap each other. We will work with this principle going forward.

Once we are done we should stop our GenServer (even though I rarely run this one ;-) but it’s good to know how to do it).

Note: you might see some warnings, that are caused by our above functions that will automatically start the server if hasn’t been started yet. You can safely ignore those for now.

LedStrip.stop(LedStrip)