10. Fledex: Coordinators (under construction)
Mix.install([
{:fledex, "~>0.7"}
])
Setup
We start with our classical setup
use Fledex
The coordinator macro
The coordinator is a component that allows to coordinate different animations and effects. This clearly is an advanced concept and before attempting it you should first make yourself very much familiar with the other Fledex features.
Every coordinator listens to the state messages on the Fledex.Utils.PubSub.channel_state/0 channel. Each coordinator can then decide on what to do with those messages and change the config of any animation or effect (like enable/disable).
To implement a coordinator is quite simple:
led_strip :john, Kino do
coordinator :switcher do
end
end
But getting things working well is not that simple. But before we go into the details, let’s take a step back.
The use case
Let’s look at a usecase where a coordinator would be useful. Let’s look at a simple animation. A rainbow pattern that rotates from left to right:
led_strip :example, Kino do
effect Fledex.Effect.Rotation, trigger_name: :example, stretch: 40, direction: :right do
animation :rainbow do
leds(10) |> rainbow()
end
end
end
We can have a second animation that does the same thing with a gradient, but moving into the other direction.
led_strip :example2, Kino do
effect Fledex.Effect.Rotation, trigger_name: :example, stretch: 40, direction: :left do
animation :gradient do
leds(10) |> gradient(:red, :blue)
end
end
end
But how do we implement for example that we want to have those two animation on the LED strip, but we want to change between them every 10sec?
A naive approach might look like the following:
require Integer
led_strip :example3, Kino do
effect Fledex.Effect.Rotation, trigger_name: :example, stretch: 40, direction: :right do
animation :rainbow do
%{swap: %{second: second}} when Integer.is_odd(trunc(second/2)) ->
leds(10) |> rainbow()
_ -> leds(10)
end
end
effect Fledex.Effect.Rotation, trigger_name: :example, stretch: 40, direction: :left do
animation :gradient do
%{swap: %{second: second}} when Integer.is_even(trunc(second/2)) ->
leds(10) |> gradient(:red, :blue)
_ -> leds(10)
end
end
job :change, ~e[*/2 * * * * * *]e do
broadcast_trigger(%{swap: Time.utc_now()})
end
end
Even though this approach works, it’s not ideal. the transitions are not smooth, we need to add the “switching” code into our animation - even though that’s not really what we want, and we have to “guess” how much time it takes from left to right.
Let’s see what happens if we add a coordinator that just looks at the events that reach it. Let’s start doing this first for a single animation. We need to provide a function that takes 3 arguments:
-
state: The state of the animation. This is very much dependent on thecontext -
context: Information where the event is coming from. Whoever triggered it determines what thestatereally means. -
options: some options that we configured the coordinator with and/or some settings that we retain betwene calls. Here we add a simple counter that increments between every call.
> ##### Note > > You might have realized that we filter the context for our animation name and don’t do anything > with those events coming from other led strips. This is because coordinators (even thoough they > are specific to a certain led strip) are getting events globaly. For now, we surely do not want > to see the events form the led strips we have defined above.
led_strip :example4, Kino do
effect Fledex.Effect.Rotation, trigger_name: :example, stretch: 40, direction: :right do
animation :rainbow do
leds(10) |> rainbow()
end
end
coordinator :coord, [] do
state, %{strip_name: :example4} = context, options ->
IO.puts(inspect {state, context, options})
Keyword.update(options, :counter, 0, fn old -> old + 1 end)
_x, _y, options -> options
end
end