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 importFledex
, to load our DSL macros (led_strip
andanimation
), and to importFledex.Leds
. This means we won’t writeLeds
in fron of functions. By default this will also start theFledex.Animation.Manager
if it’s not already running which will take care of all the animation coordinations.
use Fledex
The clock
-
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 that correspond to an animatio (and will use internally 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
as we have done in the “Fledex Clock Example“) but we will look at this a bit later.
led_strip :clock, Kino do
static :help do
leds(5) |> light(davy_s_grey()) |> repeat(12)
end
animation :hour do
_triggers ->
%{hour: hour, minute: _minute, second: _second} = Time.utc_now() |> Time.add(1, :hour)
leds(24) |> light(blue(), hour + 1)
end
animation :minute do
_triggers ->
%{hour: _hour, minute: minute, second: _second} = Time.utc_now() |> Time.add(1, :hour)
leds(60) |> light(:may_green, minute + 1)
end
animation :second do
_triggers ->
%{hour: _hour, minute: _minute, second: second} = Time.utc_now() |> Time.add(1, :hour)
leds(60) |> light(:red, 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). 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 the repeat/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 into hour
, minute
, or second
. Note: the code is a bit extra bloated to pattern match it every time into all 3 and to comment out the ones we don’t need, but that is just to highlight that we have all 3 elements but we are only interested in one of them.
Exapmle: For the second indicator the following would have been enough:
animation :second do
_triggers ->
%{second: second} = Time.utc_now() |> Time.add(1, :hour)
leds(60) |> light(:red, second + 1)
end
We then define a sequence of Leds
of the right length (24
for hour
, and 60
for minute
and second
). We switch-on only a single light in some color at the correct position (with the offset corresponding to hour
, minute
or second
)
Note: in the clock example we defined a static sequence of Leds
and offsetted it. This requires the definition of a send_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 ocasion 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.
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, 1, 20)
|> light(:may_green)
|> light(:red, 22, 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)
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(), 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})