Skip to content

Commit

Permalink
Adding leds size function
Browse files Browse the repository at this point in the history
renaming conversion functions
removing commented out code
better type checking
adding kino renderers for Leds and for colors
fixing bug in name loader
adding type to Names module
adding guard for color names
adding a color type
adding more Leds tests
updating TODO list
creating a livebook to give an introduction to colors and leds
  • Loading branch information
a-maze-d committed Nov 27, 2023
1 parent f3e4e71 commit 7be9c56
Show file tree
Hide file tree
Showing 16 changed files with 487 additions and 78 deletions.
6 changes: 4 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ as really done. Here the outstanding tasks that I can think of.
# Tasks
- [ ] Documentation
- [ ] Finish the dsl livebook example (mostly done, but the send_config part is not done yet) (v0.3)
- [ ] Create another livebook (2b) that looks at the different aspects of the Leds module and how to work with it
- [x] Create another livebook (2b) that looks at the different aspects of the Leds module and how to work with it (v0.3)
- [ ] Add proper API/module documentation (at least for the most important modules) (v0.3)
- [ ] Fledex
- [ ] Fledex.Leds
Expand All @@ -15,7 +15,9 @@ as really done. Here the outstanding tasks that I can think of.
- [ ] Fledex.Animation.LedAnimator
- [ ] Fledex.LedsDriver
- [ ] Add type specs (at least for the most important modules) (v0.3)
- [ ] Improve hexdocs (add livebooks, create cheatsheet) (v0.3)
- [ ] Improve hexdocs
- [x] add livebooks (v0.3)
- [ ] create cheatsheet (v0.3)
- [ ] Add documentation on how to connect the LED strip to a RaspberryPi Zero (with and without level shifter).This could be part of the first example (v0.3 & v0.4)
- [ ] Add installation instructions (v0.4)
- [ ] Create a dsl (domain specific language) to (finally) easily program strips
Expand Down
64 changes: 64 additions & 0 deletions lib/fledex/color/kino_render.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
defmodule Fledex.Color.KinoRender do
import Fledex.Color.Names, only: [is_color_name: 1]
alias Fledex.Color.Types
alias Fledex.Color.Utils
alias Fledex.Leds

defguard is_byte(sub_pixel) when is_integer(sub_pixel) and sub_pixel >= 0 and sub_pixel <= 255

defstruct [:colors]
@type t :: %__MODULE__{
colors: [Types.color]
}

@spec new(Types.color | [Types.color]) :: t
def new(colors) when is_list(colors) do
%__MODULE__{colors: colors}
end
def new(color) when is_integer(color) do
new([color])
end
def new({r, g, b} = color) when is_byte(r) and is_byte(g) and is_byte(b) do
new([color])
end
def new(color) when is_atom(color) and is_color_name(color) do
new([color])
end

@spec to_leds(t) :: Leds.t
def to_leds(%__MODULE__{colors: colors}) do
colors = Enum.map(colors, fn color ->
Utils.to_colorint(Utils.to_rgb(color))
end)
Leds.leds(
length(colors),
Map.new(
Enum.reduce(colors, [], fn value, acc ->
index = length(acc) + 1
[{index, value} | acc]
end)
),
%{}
)
end

@spec to_markdown(KinoRender.t) :: binary
def to_markdown(%__MODULE__{} = colors) do
Leds.to_markdown(to_leds(colors))
end

defimpl Kino.Render do
alias Fledex.Color.KinoRender

@impl true
def to_livebook(%KinoRender{} = colors) do
md_kino = Kino.Markdown.new(KinoRender.to_markdown(colors))
i_kino = Kino.Inspect.new(colors)
kino = Kino.Layout.tabs(
Leds: md_kino,
Raw: i_kino
)
Kino.Render.to_livebook(kino)
end
end
end
9 changes: 3 additions & 6 deletions lib/fledex/color/load_utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,14 @@ defmodule Fledex.Color.LoadUtils do

defp convert_to_atom(name) do
name
|> String.trim()
|> String.normalize(:nfd)
|> String.replace(~r/[^a-zA-Z0-9_]/, "_")
|> String.replace(~r/__+/, "_")
|> String.replace(~r/[^a-zA-Z0-9]/, " ")
|> String.trim()
|> String.replace(~r/\s+/, "_")
|> String.downcase(:ascii)
|> remove_trailing_underscore()
|> String.to_atom()
end

# defp remove_trailing_underscore(<<name, "_">>), do: name
defp remove_trailing_underscore(name), do: name
defp clean_and_convert(hex_string) do
hex_string = String.replace(hex_string, "#", "")
{hex_int, _} = Integer.parse(hex_string, 16)
Expand Down
6 changes: 6 additions & 0 deletions lib/fledex/color/names.ex
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ defmodule Fledex.Color.Names do
@colors colors
@color_names Enum.map(@colors, fn %{name: name} = _colorinfo -> name end)

quote do
@type t :: unquote_splicing(@color_names)
end

defguard is_color_name(atom) when atom in @color_names

@doc """
Get all the data about the predefined colors
"""
Expand Down
9 changes: 5 additions & 4 deletions lib/fledex/color/types.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule Fledex.Color.Types do
@type rgb :: {red :: 0..255, green :: 0..255, blue :: 0..255}
@type hsv :: {hue :: 0..255, saturation :: 0..255, value :: 0..255}
@type hsl :: {hue :: 0..255, saturation :: 0..255, light :: 0..255}
@type colorint :: 0..0xFFFFFF
@type rgb :: {red :: 0..255, green :: 0..255, blue :: 0..255}
@type hsv :: {hue :: 0..255, saturation :: 0..255, value :: 0..255}
@type hsl :: {hue :: 0..255, saturation :: 0..255, light :: 0..255}
@type colorint :: 0..0xFFFFFF
@type color :: rgb | colorint | atom | Fledex.Color.Names.t
end
13 changes: 7 additions & 6 deletions lib/fledex/color/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ defmodule Fledex.Color.Utils do
def nscale8(color, rgb, video) do
split_into_subpixels(color)
|> nscale8(rgb, video)
|> combine_subpixels()
|> to_colorint()
end

@doc """
Expand Down Expand Up @@ -109,19 +109,20 @@ defmodule Fledex.Color.Utils do
@doc """
This function combines the subpixels to a single (color) integer value
"""
@spec combine_subpixels(Types.rgb) :: Types.colorint
def combine_subpixels({r, g, b}) do
# TODO: make more generic to accept more color types
@spec to_colorint(Types.rgb) :: Types.colorint
def to_colorint({r, g, b}) do
(r<<<16) + (g<<<8) + b
end

@doc """
This function splits a single (color) integer value into it's rgb components
"""
@spec convert_to_subpixels((Types.colorint | atom | Types.rgb | %{rgb: Types.rgb} | %{rgb: Types.colorint})) :: Types.rgb
def convert_to_subpixels(rgb) do
@spec to_rgb((Types.colorint | atom | Types.rgb | %{rgb: Types.rgb} | %{rgb: Types.colorint})) :: Types.rgb
def to_rgb(rgb) do
case rgb do
%{rgb: {r, g, b}} -> {r, g, b}
%{rgb: x} when is_integer(x) -> convert_to_subpixels(x)
%{rgb: x} when is_integer(x) -> to_rgb(x)
x when is_atom(x) -> apply(Names, x, [:rgb])
x when is_integer(x) -> split_into_subpixels(x)
x -> x
Expand Down
2 changes: 1 addition & 1 deletion lib/fledex/led_strip_driver/spi_driver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ defmodule Fledex.LedStripDriver.SpiDriver do
binary = leds
|> Correction.apply_rgb_correction(config.color_correction)
|> Enum.reduce(<<>>, fn led, acc ->
{r, g, b} = Utils.convert_to_subpixels(led)
{r, g, b} = Utils.to_rgb(led)
acc <> <<r, g, b>>
end)
response = Circuits.SPI.transfer(config.ref, binary)
Expand Down
81 changes: 51 additions & 30 deletions lib/fledex/leds.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,14 @@ defmodule Fledex.Leds do
alias Fledex.LedsDriver

@enforce_keys [:count, :leds, :opts]
defstruct count: 0, leds: %{}, opts: %{}, meta: %{index: 1} #, fill: :none
defstruct count: 0, leds: %{}, opts: %{}, meta: %{index: 1}
@type t :: %__MODULE__{
count: integer,
leds: map,
opts: map,
meta: map
}

# @spec new() :: t
# def new do
# new(0)
# end
# @spec new(integer) :: t
# def new(count) do
# new(count, %{server_name: nil, namespace: nil})
# end
# @spec new(integer, map) :: t
# def new(count, opts) do
# new(count, %{}, opts)
# end
# @spec new(integer, map, map) :: t
# def new(count, leds, opts) do
# new(count, leds, opts, %{index: 1})
# end
# @spec new(integer, map, map, map) :: t
# def new(count, leds, opts, meta) do
# %__MODULE__{
# count: count,
# leds: leds,
# opts: opts,
# meta: meta
# }
# end
@spec leds() :: t
def leds do
leds(0)
Expand All @@ -70,6 +45,10 @@ defmodule Fledex.Leds do
}
end

@spec size(t) :: pos_integer
def size(%Leds{count: count} = _leds) do
count
end
@spec set_driver_info(t, namespace :: atom, server_name :: atom) :: t
def set_driver_info(%{opts: opts} = leds, namespace, server_name \\ Fledex.LedsDriver) do
opts = %{opts | server_name: server_name, namespace: namespace}
Expand Down Expand Up @@ -101,8 +80,8 @@ defmodule Fledex.Leds do
num_leds = opts[:num_leds] || leds.count
offset = opts[:offset] || 0

start_color = Utils.convert_to_subpixels(start_color)
end_color = Utils.convert_to_subpixels(end_color)
start_color = Utils.to_rgb(start_color)
end_color = Utils.to_rgb(end_color)

led_values = Functions.create_gradient_rgb(num_leds, start_color, end_color)
|> convert_to_leds_structure(offset)
Expand Down Expand Up @@ -145,15 +124,21 @@ defmodule Fledex.Leds do
raise ArgumentError, message: "the offset needs to be > 0 (found: #{offset})"
end
@spec light(t, (Types.colorint | t | atom), pos_integer, pos_integer) :: t
def light(leds, led, offset, repeat) do
def light(leds, led, offset, repeat) when offset > 0 and repeat > 1 do
# convert led to a LEDs struct
led = case led do
led when is_integer(led) -> __MODULE__.leds(1) |> __MODULE__.light(led)
led when is_atom(led) -> __MODULE__.leds(1) |> __MODULE__.light(led)
led when is_struct(led) -> led
%Leds{} = led -> led
end
# repeat the sequence
led = led |> __MODULE__.repeat(repeat)
# merge in the sequence at the coorect offset
__MODULE__.light(leds, led, offset)
end
def light(_leds, _led, offset, repeat) do
raise ArgumentError, message: "the offset needs to be > 0 (found: #{offset}) and repeat > 1 (found: #{repeat})"
end

@spec do_update(t, (Types.colorint | Types.rgb | atom)) :: t
defp do_update(%__MODULE__{meta: meta} = leds, rgb) do
Expand Down Expand Up @@ -186,6 +171,11 @@ defmodule Fledex.Leds do
color_int = apply(Names, atom, [:hex])
do_update(leds, color_int, offset)
end
@spec do_update(t, Types.rgb, pos_integer) :: t
defp do_update(leds, {_r, _g, _b} = rgb, offset) do
color_int = Utils.to_colorint(rgb)
do_update(leds, color_int, offset)
end
defp do_update(leds, led, offset) do
raise ArgumentError, message: "unknown data #{inspect leds}, #{inspect led}, #{inspect offset}"
end
Expand All @@ -211,6 +201,21 @@ defmodule Fledex.Leds do
end)
end

@base16 16
@block <<"\u2588">>
@spec to_markdown(Fledex.Leds.t, map) :: String.t
def to_markdown(leds, _config \\ %{}) do
leds
|> Fledex.Leds.to_list()
# |> Correction.apply_rgb_correction(config.color_correction)
|> Enum.reduce(<<>>, fn value, acc ->
hex = value
|> Integer.to_string(@base16)
|> String.pad_leading(6, "0")
acc <> "<span style=\"color: ##{hex}\">" <> @block <> "</span>"
end)
end

@spec send(t, map) :: :ok | {:error, String}
def send(leds, opts \\ %{}) do
offset = opts[:offset] || 0
Expand Down Expand Up @@ -252,4 +257,20 @@ defmodule Fledex.Leds do
offset = if rotate_left, do: offset, else: count-offset
Enum.slide(vals, 0..rem(offset-1 + count, count), count)
end

defimpl Kino.Render, for: Fledex.Leds do
alias Fledex.Leds

@impl true
@spec to_livebook(Fledex.Leds.t) :: map()
def to_livebook(leds) do
md_kino = Kino.Markdown.new(Leds.to_markdown(leds))
i_kino = Kino.Inspect.new(leds)
kino = Kino.Layout.tabs(
Leds: md_kino,
Raw: i_kino
)
Kino.Render.to_livebook(kino)
end
end
end
2 changes: 1 addition & 1 deletion lib/fledex/leds_driver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ defmodule Fledex.LedsDriver do
elems
|> Enum.map(fn elem -> Utils.split_into_subpixels(elem) end)
|> apply_merge_strategy(merge_strategy)
|> Utils.combine_subpixels()
|> Utils.to_colorint()
end

@spec apply_merge_strategy(list(Types.colorint()), atom) ::
Expand Down
Loading

0 comments on commit 7be9c56

Please sign in to comment.