Skip to content

Commit

Permalink
adding name functions to populate leds
Browse files Browse the repository at this point in the history
adding a simple dimming effect
more livebook examples
updated TODO
  • Loading branch information
a-maze-d committed Dec 23, 2023
1 parent 8ca4017 commit 3d8f5bc
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 16 deletions.
6 changes: 5 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Even though this library is published, there are things I still want to do befor
- [ ] Documentation
- [x] Finish the dsl livebook example (mostly done, but the send_config part is not done yet) (v0.3)
- [x] Create another livebook (2b) that looks at the different aspects of the Leds module and how to work with it (v0.3)
- [x] Create another livebook (7) for effects
- [x] Add proper API/module documentation (at least for the most important modules) (v0.3)
- [x] Fledex
- [x] Fledex.Leds
Expand Down Expand Up @@ -41,13 +42,15 @@ Even though this library is published, there are things I still want to do befor
- [x] Rename the LedAnimationManager to take advantage of the namespace (v0.3)
- [x] Rename the LedAnimator to just Animator (v0.3)
- [x] Rename the specific drivers to not contain Driver in the name (since it's already in the namespace) (v0.3)
- [ ] Rename the `def-func` and `send_config_func` to make it more natural in the dsl? (v0.3)
- [x] Rename the `def-func` and `send_config_func` to make it more natural in the dsl? (v0.3) <-- The send_config_func has been deprecated in favor of an effects interface
- [ ] Improve the Fledex configuration. The fledex_config/0 function feels very wrong (rethink the animator split up. Also effects raise the questions whether static "animations" should really be so static, since the effect would have any real effect on them) (v0.3 or v0.4)
- [ ] Improve the LedsDriver config (v0.4)
- [ ] Fix flaky tests (see TODOs) (v0.4)
- [ ] Perform an extra round of testing on hardware (v0.4)
- [ ] Enable Telemetry? (v0.5)
- [ ] Upgrade to a hex released version of circuits_sim as soon as available (v0.?)
- [ ] Missing functionality
- [ ] set the effect trigger in the animator to the strip name if not set
- [ ] Improve on the option handling in the fledex macros. Just pass them straight on to the animators
- [ ] see the project plan that was planned out with my son, we are not quite there yet (v0.4?)
- [ ] Connect everything into a supervision tree (to make it more robust) (v0.4)
Expand All @@ -56,6 +59,7 @@ Even though this library is published, there are things I still want to do befor
- [x] Conversions `to_rgb`, `to_colorint` <-- decided against it to allow having simple structures (tuple) instead of (module)structs. Protocols don't seem to work with those.
- [ ] ??? animations & components?
- [ ] Create a dsl (domain specific language) to (finally) easily program strips
- [x] Extend the Fledex macros with an effect marco (v0.3)
- [ ] Extend the Fledex macros to allow easy configuration with a config macro (v0.3)
- [ ] Clustering
- [x] Create a driver that outputs through pubsub (on one node) (v0.3) and
Expand Down
11 changes: 10 additions & 1 deletion lib/fledex/animation/animator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ defmodule Fledex.Animation.Animator do
@type config_t :: %{
optional(:type) => atom,
optional(:def_func) => (map -> Leds.t),
optional(:effects) => [{module, keyword}],
optional(:send_config_func) => (map -> map),
optional(:counter) => integer,
optional(:timer_ref) => reference | nil,
Expand Down Expand Up @@ -159,13 +160,21 @@ defmodule Fledex.Animation.Animator do
type: config[:type] || state.type,
triggers: Map.merge(state.triggers, config[:triggers] || state[:triggers]),
def_func: Map.get(config, :def_func, state[:def_func] || &Base.default_def_func/1),
effects: config[:effects] || state.effects,
effects: update_effects(state.effects, config[:effects], state.strip_name),
send_config_func: Map.get(config, :send_config_func, state[:send_config_func] || &Base.default_send_config_func/1),
strip_name: state.strip_name, # not to be updated
animation_name: state.animation_name # not to be updated
}
end

@spec update_effects(current_effects :: [{module, keyword}], new_effects :: [{module, keyword}], strip_name :: atom) :: [{module, keyword}]
defp update_effects(current_effects, new_effects, strip_name) do
effects = new_effects || current_effects
Enum.map(effects, fn {module, configs} ->
{module, Keyword.put_new(configs, :trigger_name, strip_name)}
end)
end

@impl GenServer
@spec handle_cast({:config, config_t}, state_t) :: {:noreply, state_t}
def handle_cast({:config, config}, state) do
Expand Down
File renamed without changes.
5 changes: 5 additions & 0 deletions lib/fledex/color/names.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ defmodule Fledex.Color.Names do

alias Fledex.Color.LoadUtils
alias Fledex.Color.Types
alias Fledex.Leds

colors = LoadUtils.load_color_file(@external_resource)

Expand Down Expand Up @@ -108,5 +109,9 @@ defmodule Fledex.Color.Names do
def unquote(name)(:hsl), do: unquote(Macro.escape(color)).hsl
def unquote(name)(:descriptive_name), do: unquote(Macro.escape(color)).descriptive_name
def unquote(name)(:source), do: unquote(Macro.escape(color)).source
@spec unquote(name)(Leds.t) :: Leds.t
def unquote(name)(leds), do: leds |> Leds.light(unquote(Macro.escape(color)).hex)
@spec unquote(name)(Leds.t, offset :: non_neg_integer) :: Leds.t
def unquote(name)(leds, offset), do: leds |> Leds.light(unquote(Macro.escape(color)).hex, offset)
end
end
21 changes: 21 additions & 0 deletions lib/fledex/effect/dimming.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule Fledex.Effect.Dimming do
@behaviour Fledex.Effect.Interface

alias Fledex.Color.Utils

@impl true
def apply(leds, _count, config, triggers) do
trigger_name = config[:trigger_name] || :default
divisor = config[:divisor] || 1
step = triggers[trigger_name] || 0
step = rem(step, 255)
step = trunc(step / divisor)

Enum.map(leds, fn led ->
led
|> Utils.to_rgb()
|> Utils.nscale8(255 - step, false)
|> Utils.to_colorint()
end)
end
end
20 changes: 20 additions & 0 deletions livebooks/3b_fledex_more_about_colors.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,26 @@ You can also retrieve the information from a specific color, like `:almond` in t
* `:source`: Information where the color comes from, see Wikipedia for more details
* `info/1` and `info/2`: They are convenience functions when you only have an atom as the color name. They map to the functions, thus `info(:almond)` is equal to `almond()` and `info(:almond, what)` is equal to `almond(what)`.

In addition each color can also be used directly in an `Leds` sequence since it provides to convenience functions:

* `almond(leds)` which takes an `Leds` sequence and adds the `:almond` color to it (at the next position). This is equivalent to:
```elixir
leds |> Leds.light(almond(:hex))
```
* `almond(leds, offset)` which takes an `Leds` sequence and an offset to specify where the led should be positioned. This is equivalent to:
```elixir
leds |> Leds.light(almond(:hex), offset)
```

This allows for a very natural way to define an led sequence. especially if we import the `Fledex.Leds` and `Fledex.Color.Names` modules.

```elixir
import Fledex.Leds
import Fledex.Color.Names

leds(5) |> almond |> red |> green |> blue(5)
```

## Color Correction

More info to follow
134 changes: 122 additions & 12 deletions livebooks/7_fledex_effects.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@ alias Fledex.Effect.Wanish
use Fledex
```

You might be wondering what an effect is. It's simply a module with a `@behaviour Fledex.Effect.Interface` implementing the simple `apply/4`. The function takes 4 parameters

* a `list` of `colorint`s, e.g. `[0xff0000, 0x00ff00, 0x0000ff]`
* a length of the list passed in as first argument. This is to avoid to walk through the list
* a keyword list with `config`uration paramers. They are the ones you specified in the `effect` macro. As an effect implementer, you can decide what kind of parameters are permitted.
* a `triggers` map containing the various triggers (like the strip counter, or some other triggers as we have seen in our weather example)

There is already a standard set of effects available all under the `Fledex.Effect` (like the ones we aliased above), but you can create your own effects. by simply implementing the behaviour.
There is already a standard set of effects available all under the `Fledex.Effect` (like the ones we aliased above), but you can create your own effects. by simply implementing the behaviour `Fledex.Effect.Interface`. We'll take a look on how to do this below.

In the following we'll take a closer look at two of the standard effects:

Expand All @@ -39,22 +32,139 @@ The latter one we have already encountered under the hood, without realizing it.

The wanish effect makes some leds vanish starting from one end and extending to infity. The effect can be controled through a couple of conig parameters:

* `:trigger_name`: this is the name that will be used as the steering factor for the effect. If nothing is specified the animation will use the `led_strip`name as trigger name (in this case `:john` will be used.)
* `:trigger_name`: this is the name that will be used as the steering factor for the effect. If nothing is specified the animation will use the `led_strip`name as trigger name (in this case `:john` will be used, so we don't need to specify it).
* `:divisor`: the trigger might come with a too high frequency and you want to slow down the wanishing effect. The Divisor will slow it down with that factor. (default: 1, i.e. no slow down)
* `:direction`: the direction specifies whether the wanishing should happen from the start (`:left`) or from the end (`:right`) of the led_strip
* `:circulate`: indicates whether we want to warp around and redo the wanishing once the first round has finished. In that case the full led sequence will be visible and will wanish again. Note: if you use `:reappear` this will automatically be set.
* `:reappear`: this will enable the `:circulate` and instead of all leds appearing at the same time, they will first slowly reappear (one by one) and only once they are all visible again will they disappear again. This setting comes with an extra `:reappear_key`, but in most cases you don't need to worry about it, because the default will be just fine.
* `:switch_on_off_func`: There is also a possibility to control when you want the effect to be switched on or off, but this is outside the scope of this description.

Here is an example on shuch an effect on a simple led sequence
Here is an example on such an effect on a simple led sequence

```elixir
led_strip :john, :kino do
effect Wanish, trigger_name: :john, divisor: 2, direction: :left, reappear: true do
effect Wanish, divisor: 2, direction: :right, reappear: true do
animation :test do
_triggers ->
leds(50) |> rainbow()
leds(50) |> rainbow
end
end
end
```

## The Rotation effect

As mentioned above, we have used the functionality of the rotation effect already. It is only a very simple version and it can't be combined with other effects. Therefore let's look at the rotation once more, this time as an effect.

The available configuration parameters are:

* `:trigger_name`: This is again the trigger that will drive the animation. If nothing is specified, the `Fledex.Animation.Animator` will set the `led_strip` name as the default. Therefore you rarely have to set it manually.
* `:direction`: The rotation can be applied into two directions: `:left` (default) and `:right`
* `:divisor`: As before this allows to slow down the animation, by not updating it on every trigger event.

Let's see how this looks if we rely on the default values:

```elixir
led_strip :rotation, :kino do
effect Rotation do
animation :rainbow do
_triggers -> leds(50) |> rainbow
end
end
end
```

## Combining effects

It is possble to combine several effects. The order of the effects can be imporant, so make sure you apply them in the correct order.

Let's try it out by combining our rotation and wanishing effect on a rainbow color spectrum.

<!-- livebook:{"break_markdown":true} -->



```elixir
led_strip :rotation_and_wanish, :kino do
effect Rotation do
effect Wanish, divisor: 4, direction: :right, reappear: true do
animation :rainbow do
_triggers -> leds(50) |> rainbow
end
end
end
end
```

## BYOE: Bring Your Own Effect

It is maybe nice to use pre-exisiting effects, but it's much more exciting to create your own effect. In this example we are looking on how to do that.

You might be wondering what an effect is. It's simply a module with a `@behaviour Fledex.Effect.Interface` implementing the simple `apply/4` function. The function takes 4 parameters

* a `list` of `colorint`s, e.g. `[0xff0000, 0x00ff00, 0x0000ff]`
* a length of the list passed in as first argument. This is to avoid to walk through the list to figure this out.
* a keyword list with `config`uration parameters. They are the ones you specified in the `effect` macro. As an effect implementer, you can decide what kind of parameters are permitted. There is one parameter that will be populated by the `Fledex.Animation.Animator` if it's not specified, which is the `:trigger_name`. It is the trigger that drives your effect.
* a `triggers` map containing the various triggers (like the strip counter, or some other triggers as we have seen in our weather example)

Let's start to define our own module and the function. The simplest implementation is to simply return the leds array.

```elixir
defmodule LivebookEffect1 do
@behaviour Fledex.Effect.Interface

@impl true
@spec apply(
leds :: [Types.colorint()],
count :: non_neg_integer,
config :: keyword,
triggers :: map
) ::
list(Types.colorint()) | {list(Types.colorint()), map}
def apply(leds, _count, _config, _triggers) do
leds
end
end
```

This is of course not very exciting, since effect has no effect and is thereby useless. But it's a good starting point.

From the `@spec` we can see that we have two alternative return values, either just the leds or a tuple with leds and a map. The map is the trigger map, that can be used to carry state between different execution rounds. In this example we won't make use of this though.

Now let's create a new effect. What about going crazy and to randomize the leds?

This is very easy to accomplish, we only need to call the `Enum.shuffle/1` function.

```elixir
defmodule LivebookEffect2 do
@behaviour Fledex.Effect.Interface

@impl true
@spec apply(
leds :: [Types.colorint()],
count :: non_neg_integer,
config :: keyword,
triggers :: map
) ::
list(Types.colorint()) | {list(Types.colorint()), map}
def apply(leds, _count, _config, _triggers) do
Enum.shuffle(leds)
end
end
```

That's it already, now we can already use our own effect as we have used an effect above. Let's see how crazy this looks.

```elixir
led_strip :merry, :kino do
effect LivebookEffect2 do
animation :xmas do
_triggers -> leds(50) |> rainbow
end
end
end
```

```elixir

```
7 changes: 6 additions & 1 deletion livebooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,9 @@ Therefore we fetch the information at regular intervals, outside of our animatio
# 6. Fledex: DSL
In this example we will look at the Fledex DSL that removes a lot of the boilerplate and therefore makes the definition of an LED strip even easier. Under the hood it uses the same principles as the ones we have encountered in the examples 3-5.

In this example we will reimplement the Clock, but this time with our DSL.
In this example we will reimplement the Clock, but this time with our DSL.

# 7. Fledex: Effects
In this chapter we take a look at the effect functionality. An animation can be "spiced" with an effect. This also exists as part of the Fledex DSL.

We won't look at all possible effects, but at some examples. It is quite easy to create your own effect and we'll take a look on how to do that.
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ defmodule Fledex.MixProject do
"livebooks/3b_fledex_more_about_colors.livemd",
"livebooks/4_fledex_clock_example.livemd",
"livebooks/5_fledex_weather_example.livemd",
"livebooks/6_fledex_dsl.livemd"
"livebooks/6_fledex_dsl.livemd",
"livebooks/7_fledex_effects.livemd"
],
groups_for_extras: [
"LiveBooks": ~r/livebooks/
Expand Down

0 comments on commit 3d8f5bc

Please sign in to comment.