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

8. Fledex: Component

livebooks/8_fledex_component.livemd

8. Fledex: Component

Mix.install([
  {:fledex, "~>0.4"}
])

Setup

use Fledex

The Weather animation (with a component)

Instead of defining the complicated logic of the termometer indicator, we use this time a component that has all this logic build in. The component defines our configuration and all we have to do is to specify the mandatory (and if desired optional) parameters.

alias Fledex.Component.Thermometer

led_strip :john, :kino do
  component(:thermo, Thermometer,
    range: -20..40,
    trigger: :temperature,
    negative: :blue,
    null: :may_green,
    positive: :red,
    marker: :davy_s_grey
  )
end
Fledex.Utils.PubSub.broadcast(:fledex, "trigger", {:trigger, %{temperature: 17.2}})

To test our animation we fake again the temperature by simply publishing the desired temperature.

What is a component

The question now is just “what is a component”?

The component is simply a module that implements the @behaviour Fledex.Component.Interface. Only a single configure/2 function is required that returns a Fledex.Animation.Manager.config_t structure and thus can define anything you normaly would define through the animation, static, … macros.

To get the correct behaviour the component gets 2 parameters:

  1. The name of the component: Each animation gets it’s own name, so does the component. It should be noted that an component can create several animations and the component should make sure to name each of those individually. The termometer has two “animations” the scale and the markers on the scale. The former gets the name of the component and the latter gets the name with .helper attached.
  2. a keyword list of options: The list of parameters allows us to configure our component. Each component can define their own set of components. Probably a reoccuring parameter is :trigger_name wich allows us to configure runtime parameters, but this is by convention only.

As you have seen a component is nothing special but it allows to redefine a reusable structure. A component can be defined and distributed through an external library.

Netsted components

We now take a slightly different approach for creating our component. As we have seen above a component is simply a configuration and the dsl is simply an easier way to write a config. Thus, we can reuse one within the other.

alias Fledex.Component.Dot

led_strip :nested_components, :kino do
  component(:minute, Dot, color: :red, count: 60, trigger_name: :minute)
  component(:hour, Dot, color: :blue, count: 24, trigger_name: :hour)

  static :helper do
    leds(5) |> light(:davy_s_grey, 5) |> repeat(12)
  end
end
Fledex.Utils.PubSub.simple_broadcast(%{hour: 17, minute: 34})

The interesting thing in the Fledex.Component.Dot component is that it actually defines the component by using the Fledex.animation/3 macro. The code looks like the following

  def configure(name, options) when is_atom(name) and is_list(options) do
    use Fledex
    # ... here comes some option extraction stuff
    animation name do
      triggers when is_map(triggers) and is_map_key(triggers, trigger_name) ->
        leds(count) |> light(color, triggers[trigger_name])
      _ -> leds()
    end
  end

The clock component

The Fledex.Component.Clock component is an interesting one, because it defines several animations and even a static pattern. For that it uses the special led_strip driver :config that simply returns the config instead of delegating to the Fledex.Animation.Manager.

Each animation requires its own unique name and this is accomplished with a small utility function that combines the strip name (the one we get in) and an additional fraction.

  def configure(name, options) do
    # ... here comes some option extraction stuff 
    use Fledex
    led_strip name, :config do
      component :minute, Dot, color: minute_color, count: 60, trigger_name: create_name(trigger_name, :minute)
      component :hour, Dot, color: hour_color, count: 24, trigger_name: create_name(trigger_name, :hour)
      static create_name(trigger_name, :helper) do
        leds(5) |> light(helper_color, 5) |> repeat(12)
      end
    end
  end

As you can see it defines, more or less, the same animations as our example above. Thus, instead of us defining the different animations, the component is doing this for us. To use the component, we now simply have to use the clock and pass in the necessary options, and that’s it!

alias Fledex.Component.Clock
import Crontab.CronExpression

led_strip :nested_components2, :kino do
  component({:clock_hour, :clock_minute}, Clock, trigger_name: :clock)

  job :clock, ~e[* * * * * * *]e do
    date_time = DateTime.utc_now()
    IO.puts("ok, checking... #{date_time}")

    Fledex.Utils.PubSub.simple_broadcast(%{
      clock_hour: date_time.hour,
      clock_minute: date_time.minute
    })
  end
end

And to see the correct time, we only need to publish the appropriate time. When we do so we shouldn’t forget that the animations are reacting to two triggers that we passed in. We need to publish the triggers :clock_hour and :clock_minute as triggers.

Fledex.Utils.PubSub.simple_broadcast(%{clock_hour: 17, clock_minute: 34})