Powered by AppSignal & Oban Pro

10. Fledex: Coordinators (under construction)

livebooks/10_fledex_coordinators.livemd

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 the context
  • context: Information where the event is coming from. Whoever triggered it determines what the state really 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