diff --git a/README.md b/README.md index b953d28..8f4db2a 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,12 @@ A library to provide a Scenic framework driver implementation for SPI serial con This driver only runs on RPi devices as far as we know as it is based on the scenic rpi driver generating a framebuffer we can use. +Many thanks to the work on the Inky Scenic Driver folks (https://github.com/pappersverk/scenic_driver_inky) who worked through the framebuffer capturing from rpi, and Frank Hunleth for his tireless work (https://github.com/fhunleth/rpi_fb_capture) (as always) + ## Installation The package can be installed -by adding `scenic_driver_inky` to your list of dependencies in `mix.exs`: +by adding `scenic_driver_fb_tft` to your list of dependencies in `mix.exs`: ```elixir def deps do diff --git a/lib/scenic_driver_fb_tft.ex b/lib/scenic_driver_fb_tft.ex new file mode 100644 index 0000000..c8eb81b --- /dev/null +++ b/lib/scenic_driver_fb_tft.ex @@ -0,0 +1,59 @@ +defmodule ScenicDriverFBTFT do + use Scenic.ViewPort.Driver + + alias Scenic.ViewPort.Driver + require Logger + + @default_refresh_rate 200 + @minimal_refresh_rate 100 + + @impl true + def init(viewport, size, config) do + vp_supervisor = vp_supervisor(viewport) + {:ok, _} = Driver.start_link({vp_supervisor, size, %{module: Scenic.Driver.Nerves.Rpi}}) + + interval = + cond do + is_integer(config[:interval]) and config[:interval] > 100 -> config[:interval] + is_integer(config[:interval]) -> @minimal_refresh_rate + true -> @default_refresh_rate + end + + {width, height} = size + {:ok, cap} = RpiFbCapture.start_link(width: width, height: height, display: 0) + + Process.send_after(self(), :capture, 4_000) + + {:ok, + %{ + viewport: viewport, + size: size, + cap: cap, + last_crc: -1, + interval: interval + }} + end + + @impl true + def handle_info(:capture, state) do + {:ok, frame} = RpiFbCapture.capture(state.cap, :rgb565) + + crc = :erlang.crc32(frame.data) + + if crc != state.last_crc do + File.write("/dev/fb1", frame.data) + end + + Process.send_after(self(), :capture, state.interval) + {:noreply, %{state | last_crc: crc}} + end + + defp vp_supervisor(viewport) do + [supervisor_pid | _] = + viewport + |> Process.info() + |> get_in([:dictionary, :"$ancestors"]) + + supervisor_pid + end +end diff --git a/lib/scenic_driver_inky.ex b/lib/scenic_driver_inky.ex deleted file mode 100644 index cc2a96e..0000000 --- a/lib/scenic_driver_inky.ex +++ /dev/null @@ -1,194 +0,0 @@ -defmodule ScenicDriverInky do - use Scenic.ViewPort.Driver - - alias Scenic.ViewPort.Driver - require Logger - - @dithering_options [:halftone, false] - @color_affinity_options [:high, :low] - - @default_color_affinity :low - @default_color_high 180 - @default_color_low 75 - @default_dithering false - - # This driver is not in a hurry, fetch a new FB once a second at most. - # Refresh takes 10+ seconds, this is practically hectic. - @default_refresh_rate 1000 - # Lower doesn't make much sense for Inky - @minimal_refresh_rate 100 - - @impl true - def init(viewport, size, config) do - vp_supervisor = vp_supervisor(viewport) - {:ok, _} = Driver.start_link({vp_supervisor, size, %{module: Scenic.Driver.Nerves.Rpi}}) - - type = config[:type] - accent = config[:accent] - - opts = - cond do - is_map(config[:opts]) -> config[:opts] - true -> %{} - end - - dithering = - cond do - config[:dithering] in @dithering_options -> config[:dithering] - true -> @default_dithering - end - - interval = - cond do - is_integer(config[:interval]) and config[:interval] > 100 -> config[:interval] - is_integer(config[:interval]) -> @minimal_refresh_rate - true -> @default_refresh_rate - end - - color_affinity = - cond do - config[:color_affinity] in @color_affinity_options -> config[:color_affinity] - true -> @default_color_affinity - end - - color_low = - cond do - is_integer(config[:color_low]) and config[:color_low] >= 0 -> config[:color_low] - true -> @default_color_low - end - - color_high = - cond do - is_integer(config[:color_high]) and config[:color_high] > color_low -> config[:color_high] - true -> @default_color_high - end - - {:ok, inky_pid} = Inky.start_link(type, accent, opts) - - {width, height} = size - {:ok, cap} = RpiFbCapture.start_link(width: width, height: height, display: 0) - - send(self(), :capture) - - {:ok, - %{ - viewport: viewport, - size: size, - inky_pid: inky_pid, - cap: cap, - last_crc: -1, - dithering: dithering, - color_affinity: color_affinity, - color_high: color_high, - color_low: color_low, - interval: interval - }} - end - - @impl true - def handle_info(:capture, state) do - {:ok, frame} = RpiFbCapture.capture(state.cap, :rgb24) - - crc = :erlang.crc32(frame.data) - - if crc != state.last_crc do - pixel_data = process_pixels(frame.data, state) - - Inky.set_pixels(state.inky_pid, pixel_data) - end - - Process.send_after(self(), :capture, state.interval) - {:noreply, %{state | last_crc: crc}} - end - - defp process_pixels(data, %{ - size: {width, _height}, - dithering: dithering, - color_high: color_high, - color_low: color_low, - color_affinity: color_affinity - }) do - pixels = for <>, do: {r, g, b} - - {_, _, pixel_data} = - Enum.reduce(pixels, {0, 0, %{}}, fn pixel, {x, y, pixel_data} -> - pixel = process_color(pixel, x, y, color_high, color_low, color_affinity, dithering) - - color = pixel_to_color(pixel) - pixel_data = Map.put(pixel_data, {x, y}, color) - - {x, y} = - if x >= width - 1 do - {0, y + 1} - else - {x + 1, y} - end - - {x, y, pixel_data} - end) - - pixel_data - end - - defp process_color({r, g, b}, x, y, color_high, color_low, color_affinity, dithering) do - { - process_color(r, x, y, color_high, color_low, color_affinity, dithering), - process_color(g, x, y, color_high, color_low, color_affinity, dithering), - process_color(b, x, y, color_high, color_low, color_affinity, dithering) - } - end - - defp process_color(color_value, x, y, color_high, color_low, color_affinity, dithering) - when is_integer(color_value) do - if color_value > color_high do - 255 - else - if color_value < color_low do - 0 - else - dither(dithering, color_affinity, x, y) - end - end - end - - defp dither(false, color_affinity, _, _) do - case color_affinity do - :high -> 255 - :low -> 0 - end - end - - defp dither(:halftone, _, x, y) do - draw = 255 - blank = 0 - odd_x = rem(x, 2) == 0 - odd_y = rem(y, 2) == 0 - - case odd_x do - true -> - case odd_y do - true -> draw - false -> blank - end - - false -> - case odd_y do - true -> blank - false -> draw - end - end - end - - defp pixel_to_color({0, 0, 0}), do: :black - defp pixel_to_color({255, 255, 255}), do: :white - defp pixel_to_color(_), do: :accent - - defp vp_supervisor(viewport) do - [supervisor_pid | _] = - viewport - |> Process.info() - |> get_in([:dictionary, :"$ancestors"]) - - supervisor_pid - end -end diff --git a/mix.exs b/mix.exs index 039ec09..116a9f5 100644 --- a/mix.exs +++ b/mix.exs @@ -5,8 +5,8 @@ defmodule ScenicDriverInky.MixProject do def project do [ - app: :scenic_driver_inky, - version: "1.0.0", + app: :scenic_driver_fb_tft, + version: "0.1.0", elixir: "~> 1.8", description: description(), package: package(), @@ -28,22 +28,21 @@ defmodule ScenicDriverInky.MixProject do {:scenic, "~> 0.9"}, {:scenic_driver_nerves_rpi, "~> 0.9", targets: @pi_targets}, {:rpi_fb_capture, "~> 0.1", targets: @pi_targets}, - {:inky, "~> 1.0.0"}, {:ex_doc, ">= 0.0.0", only: :dev} ] end defp description do - "Pimoroni Inky driver for Scenic" + "Framebuffer TFT driver for Scenic" end defp package do %{ - name: "scenic_driver_inky", + name: "scenic_driver_fb_tft", description: - "A library to provide a Scenic framework driver implementation for the Inky series of eInk displays from Pimoroni. Built on top of the pappersverk/inky library. All in Elixir.", + "A library to provide a Scenic framework driver implementation for generic TFT devices over SPI.", licenses: ["Apache-2.0"], - links: %{"GitHub" => "https://github.com/lawik/scenic_driver_inky"} + links: %{"GitHub" => "https://github.com/makerion/scenic_driver_fb_tft"} } end end diff --git a/test/scenic_driver_oled_bonnet_test.exs b/test/scenic_driver_oled_bonnet_test.exs deleted file mode 100644 index 355d161..0000000 --- a/test/scenic_driver_oled_bonnet_test.exs +++ /dev/null @@ -1,4 +0,0 @@ -defmodule ScenicDriverOLEDBonnetTest do - use ExUnit.Case - doctest ScenicDriverOLEDBonnet -end