6. Fledex: DSL
# 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"}
])
Intro
We now define our animations with our DSL with the following steps:
-
First we have to
use Fledex
in order to-
load our
Fledex
DSL (likeled_strip
,animation
and the like) and -
to import commonly used functions, like
Fledex.Leds
,Fledex.Color.Names
. This means we won’t writeLeds
in fron of functions anymore. -
to start (by default) the
Fledex.Animation.Manager
if it’s not already running, who will take care of all the animation coordinations.
-
load our
use Fledex
The clock
We now go back to our clock example and rewrite it with our DSL:
-
We define a new strip with a name (
:clock
), and configure it (we use theKino
default driver for display). This will internally create a newFledex.LedStrip
server (if it’s not already running) that will control the strip. Its server name will be identical to theled_strip
name. -
Then within this strip we define several
animation
s (which will internally use theFledex.Animation.Animator
). Each animation is identified by its own name (:help
,:hour
,:minute
,:second
). - Within each animation we define a function that will be executed repeatedly.
-
Inside that function we can define one or several
Leds
, either directly or through an led sequence (like a rainbow function) and animate those leds depending on some trigger data (data that comes into the animation function). An explanation of what we are doing in our example here will be given after the code.
It is also possble to define some other configurations (like the send_config_func
that we used in the “Fledex Clock Example“) but we don’t need it right now.
led_strip :clock, Kino do
# 1.
static :help do
leds(5) |> light(davy_s_grey()) |> repeat(12)
end
# 2.
animation :hour do
_triggers ->
%{hour: hour, minute: _minute, second: _second} = Time.utc_now() |> Time.add(1, :hour)
# 3.
leds(24) |> light(blue(), offset: hour + 1)
end
# 2.
animation :minute do
_triggers ->
%{hour: _hour, minute: minute, second: _second} = Time.utc_now() |> Time.add(1, :hour)
# 3.
leds(60) |> light(:may_green, offset: minute + 1)
end
# 2.
animation :second do
_triggers ->
%{hour: _hour, minute: _minute, second: second} = Time.utc_now() |> Time.add(1, :hour)
# 3.
leds(60) |> light(:red, offset: second + 1)
end
end
The above code should result in an animation displaying the clock.Now it’s time to look at the different animation parts and how they work.
-
We first define a
static
animation (one that doesn’t animate). This animation does not get any triggers and will only be called once during the initalization (since it’s static). Notice that this function gets no argument (we don’t have the trailing_triggers ->
part that you find in the other parts). It is used to make it easier to read the time by having a dash every 5 steps and those do not change over time. The markers are repeating in a regular pattern, so we only define the first one and then repeat it with therepeat/2
function 12 times. -
For all other animations we make a call to the system time (
Time.utc_now()
) and therefore don’t require the_trigger
information. We shift the result to the right timezone (we add 1 hour, for UTC+1). We pattern match the result, depending on the animation intohour
,minute
, orsecond
. -
We then define a sequence of
Leds
of the right length (24
forhour
, and60
forminute
andsecond
). We switch-on only a single light in some color at the correct position (with the offset corresponding tohour
,minute
orsecond
)
Notes:
-
in classical Erlang/Elixir style the underscore (
_
) in_triggers
indicates that the variable is not used. -
The DSL allows you to remove the
_trigger ->
part in an animation if you don’t need it. -
The code could have been more compact by also just matching the part that we are interested in. Also considering the previous point the
:second
indicator could have been defined like this:animation :second do %{second: second} = Time.utc_now() |> Time.add(1, :hour) leds(60) |> light(:red, offset: second + 1) end
-
In the clock example we defined a static sequence of
Leds
and offsetted it. This required the definition of asend_config_func
. We will look on how to do it with the DSL as a next step.
Clock (with send function)
We are now implementing the above example, but demonstrating on how the :second
, :minute
, and :hour
indicators can be implemented in exactly the same way as we did in chapter 4, i.e. with a send function that shifts the red marker depending on the time.
We also take the occasion to demonstrate on how you can define a second strip, by giving it a new name (:clock2
). To demonstrate that it’s really a new stip this clock here displays the time in UTC (we don’t add the extra hour of offset). Look carefully at the blue hour indicator.
Also, the above strip should continue to run and a new strip will appear below the code. Had we called the strip the same as above (i.e. :clock
), then the above output would have stopped updating and a new one would appear here.
Try it out and modify any of the two animations. They should be fully independent.
led_strip :clock2, Kino do
static :help do
leds(5) |> light(davy_s_grey()) |> repeat(12)
end
animation :hour,
send_config: fn _triggers ->
%{hour: hour, minute: _minute, second: _second} = Time.utc_now()
[offset: hour, rotate_left: false]
end do
_triggers ->
leds(24) |> light(blue())
end
animation :minute,
send_config: fn _triggers ->
%{hour: _hour, minute: minute, second: _second} = Time.utc_now()
[offset: minute, rotate_left: false]
end do
_triggers ->
leds(60) |> light(:may_green)
end
animation :second,
send_config: fn _triggers ->
%{hour: _hour, minute: _minute, second: second} = Time.utc_now()
[offset: second, rotate_left: false]
end do
_triggers ->
leds(60) |> light(:red)
end
end
The weather app
We now implement the weather app (indicating the temperature) we implemented in chapter 5 by using our DSL.
led_strip :weather, Kino do
animation :temperature do
triggers ->
temp = triggers[:temperature] || 100
temp = round(temp)
temp =
if temp == 100 do
100
else
temp = min(temp, 40)
max(temp, -20)
end
{leds, offset} =
case temp do
temp when temp == 100 ->
{leds(61)
|> light(:blue, offset: 1, repeat: 20)
|> light(:may_green)
|> light(:red, offset: 22, repeat: 40), 1}
temp when temp < 0 ->
{leds(1) |> light(:blue) |> repeat(temp), 21 + temp}
temp when temp == 0 ->
{leds(1) |> light(:may_green), 21}
temp when temp > 0 ->
{leds(1) |> light(:red) |> repeat(temp), 22}
end
leds(61) |> light(leds, offset: offset)
end
static :help do
# we allow temperatures down to -20C and up to +40C
negative_scale =
leds(5)
|> light(davy_s_grey())
|> repeat(4)
positive_scale =
leds(5)
|> light(davy_s_grey(), offset: 5)
|> repeat(8)
leds(61)
|> light(negative_scale)
|> light(davy_s_grey())
|> light(positive_scale)
end
end
This time we don’t implement the connection to the weather API, but we only fake it.
Fledex imports Fledex.Utils.PubSub
and therefore we can use the broadcast_trigger/1
function directly.
broadcast_trigger(%{temperature: 17.2})