8. Fledex: Component
Mix.install([
{:fledex, "~>0.5"}
])
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.configs_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:
-
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. -
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
broadcast_trigger(%{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(:myclock, Clock, trigger_name: :clock)
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 (or if we like add even teh :clock_second
too).
broadcast_trigger(%{clock_hour: 17, clock_minute: 34, clock_second: 12})
A job to drive the clock
Regular updates is something so common that we’ll look at the job
macro that will allows to schedule udpates in regular intervals. We will take a closer look at this in another livebook but here already a sneak preview.
alias Fledex.Component.Clock
led_strip :nested_components3, Kino do
component(:myclock2, Clock, trigger_name: :clock2)
job :clock, ~e[* * * * * * *]e do
%DateTime{hour: hour, minute: minute, second: second} = DateTime.utc_now()
broadcast_trigger(%{
clock2_hour: hour,
clock2_minute: minute,
clock2_second: second
})
end
end
Debugging tricks
When you develop components it can sometimes be difficult to debug them especially if you nest other components into your own. You easily will run into the question “Why does my component not show what I expect it to show?”. The issue is usually that the triggers are not properly configured. But figuring out where it goes wrong is not so easy.
There is a quite simply trick to debug a component by adding the following animation to your component:
animation :logger do
%{logger: logger} = trigger ->
if rem(logger, 10) == 0 do
IO.puts "logger: #{inspect trigger}"
end
{leds(), %{trigger | logger: logger+1}}
trigger -> {leds(), Map.put_new(trigger, :logger, 0)}
end
It will log the triggers in regular intervals so you see what the component receives and you can then compare it with what you would expect.
As you can see this animation has no effect on your animation, because it always returns empty leds()
. We only use the side effect (writing to IO.puts
) of this animation.