diff --git a/config/config.exs b/config/config.exs index 139e59f..3bcf6bd 100644 --- a/config/config.exs +++ b/config/config.exs @@ -6,6 +6,8 @@ import Config button_pins = {24, 25, 5, 6} +buttons = 4 +leds_per_button = 7 config :ex_speed_game, target: Mix.target(), @@ -14,20 +16,23 @@ config :ex_speed_game, debug: true, # Amount of buttons in the game. - buttons: 4, + buttons: buttons, + + # How many LEDs are in the NeoPixel chain inside each button + leds_per_button: leds_per_button, + + # Colors of the LEDs keyed by index + led_colors: %{ + 0 => {31, 133, 222}, + 1 => {65, 222, 31}, + 2 => {241, 229, 0}, + 3 => {250, 51, 51} + }, # Pins of the buttons, in order from left to right in the case. Leftmost will be 0, and number # will increase to the right. button_pins: button_pins, - # Pins of the LED lights of the buttons mapped to the button pins. - button_light_pins: { - {17, elem(button_pins, 0)}, - {27, elem(button_pins, 1)}, - {22, elem(button_pins, 2)}, - {23, elem(button_pins, 3)} - }, - # Debounce delay, i.e. how long a button must be high or low before it is accepted, to reduce # spurious inputs. In milliseconds. debounce_delay: 20, @@ -51,13 +56,13 @@ config :ex_speed_game, iface: "wlan0", # LED pattern to show when there is a crash - crash_pattern: {false, true, true, false}, + crash_pattern: [false, true, true, false], # Time to show crash pattern for crash_pattern_delay: 3_000, # LED pattern to show when game ends - fail_pattern: {true, true, true, true} + fail_pattern: [true, true, true, true] # Customize non-Elixir parts of the firmware. See # https://hexdocs.pm/nerves/advanced-configuration.html for details. @@ -89,6 +94,41 @@ config :ex_speed_game, ExSpeedGame.Web.Endpoint, config :phoenix, :json_library, Jason config :phoenix, :stacktrace_depth, 20 +gamma = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, + 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, + 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, + 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, + 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, + 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, + 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, + 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 112, 114, +115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142, +144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175, +177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, +215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255 +] + +config :blinkchain, canvas: {buttons * leds_per_button, 1} + +config :blinkchain, :channel0, + pin: 18, + type: :grb, + brightness: 32, + gamma: gamma, + arrangement: [ + %{ + type: :strip, + origin: {0, 0}, + count: buttons * leds_per_button, + direction: :right + } + ] + if Mix.target() != :host do import_config "target.exs" end diff --git a/lib/application.ex b/lib/application.ex index 1f2c192..4fcc855 100644 --- a/lib/application.ex +++ b/lib/application.ex @@ -31,7 +31,6 @@ defmodule ExSpeedGame.Application do # List all child processes to be supervised def children(_target) do pins = Application.get_env(:ex_speed_game, :button_pins) - led_pins = Application.get_env(:ex_speed_game, :button_light_pins) debounce_delay = Application.get_env(:ex_speed_game, :debounce_delay) [ @@ -40,7 +39,7 @@ defmodule ExSpeedGame.Application do [name: ExSpeedGame.PubSub, keys: :duplicate, partitions: System.schedulers_online()]}, {ButtonInput, %ButtonInput.Options{pins: pins, debounce_delay: debounce_delay, name: ButtonInput}}, - {Lights, %Lights.Options{light_pins: led_pins, name: Lights}}, + {Lights, %Lights.Options{name: Lights}}, {Menu, %Menu.Options{name: Menu}}, {DynamicSupervisor, strategy: :one_for_one, name: GameSupervisor}, diff --git a/lib/game/lights.ex b/lib/game/lights.ex index 1f256c1..e7f1445 100644 --- a/lib/game/lights.ex +++ b/lib/game/lights.ex @@ -3,47 +3,38 @@ defmodule ExSpeedGame.Game.Lights do use Bitwise, only_operators: true import ExSpeedGame.Utils.TypedStruct - alias ExSpeedGame.Game.Types + require Logger - @type values :: {boolean(), boolean(), boolean(), boolean()} + @type values :: [boolean()] + @leds_per_button Application.compile_env!(:ex_speed_game, :leds_per_button) + @led_colors Application.compile_env!(:ex_speed_game, :led_colors) + @black {0, 0, 0} defmodule Options do deftypedstruct(%{ - light_pins: Types.led_pins(), name: GenServer.name() }) end defmodule State do - deftypedstruct(%{ - lights: {Types.pin(), Types.pin(), Types.pin(), Types.pin()}, - references: [reference()] - }) + deftypedstruct(%{}) end ### SERVER INTERFACE @spec start_link(Options.t()) :: :ignore | {:error, any} | {:ok, pid} def start_link(%Options{} = opts) do - {{pin1, _}, {pin2, _}, {pin3, _}, {pin4, _}} = opts.light_pins - GenServer.start_link( __MODULE__, - %{pins: {pin1, pin2, pin3, pin4}}, + %{}, name: opts.name ) end @impl true - @spec init(%{pins: {Types.pin(), Types.pin(), Types.pin(), Types.pin()}}) :: {:ok, State.t()} - def init(%{pins: pins}) do - references = - for pin <- Tuple.to_list(pins) do - {:ok, ref} = Circuits.GPIO.open(pin, :output, initial_value: 0) - ref - end - - {:ok, %State{lights: pins, references: references}} + @spec init(any()) :: {:ok, State.t()} + def init(_) do + {:ok, %State{}} end @impl true @@ -52,21 +43,28 @@ defmodule ExSpeedGame.Game.Lights do GenServer.from(), State.t() ) :: {:reply, :ok, State.t()} - def handle_call({:set_lights, values}, _from, %State{references: references} = state) do - references + def handle_call({:set_lights, values}, _from, %State{} = state) do + values |> Enum.with_index() - |> Enum.each(fn {ref, i} -> - Circuits.GPIO.write(ref, elem(values, i) |> bool2val()) + |> Enum.each(fn + {true, i} -> + from = index2coordinates(i) + Logger.debug("Setting #{i} to #{inspect(index2color(i))} #{inspect(from)}") + Blinkchain.fill(from, @leds_per_button, 1, index2color(i)) + + {false, i} -> + from = index2coordinates(i) + Logger.debug("Setting #{i} to black #{inspect(from)}") + Blinkchain.fill(from, @leds_per_button, 1, @black) end) + Logger.debug(fn -> "Setting lights #{inspect(values)}" end) + + Blinkchain.render() + {:reply, :ok, state} end - @spec bool2val(boolean()) :: Circuits.GPIO.value() - defp bool2val(bool) - defp bool2val(true), do: 1 - defp bool2val(false), do: 0 - ### CLIENT INTERFACE @doc """ @@ -82,7 +80,7 @@ defmodule ExSpeedGame.Game.Lights do """ @spec clear(GenServer.name()) :: :ok | no_return() def clear(server) do - set(server, {false, false, false, false}) + set(server, [false, false, false, false]) end @doc """ @@ -99,16 +97,29 @@ defmodule ExSpeedGame.Game.Lights do """ @spec set_index(GenServer.name(), integer()) :: :ok | no_return() def set_index(server, index) do - set(server, {index == 1, index == 2, index == 3, index == 4}) + set(server, [index == 1, index == 2, index == 3, index == 4]) end @spec number2binary(integer()) :: values() defp number2binary(number) do - { + [ (number &&& 0b1000) != 0, (number &&& 0b0100) != 0, (number &&& 0b0010) != 0, (number &&& 0b0001) != 0 + ] + end + + @spec index2coordinates(0..3) :: Blinkchain.Point.t() + defp index2coordinates(index) do + %Blinkchain.Point{ + x: index * 7, + y: 0 } end + + @spec index2color(0..3) :: {pos_integer(), pos_integer(), pos_integer()} + def index2color(index) do + Map.fetch!(@led_colors, index) + end end diff --git a/lib/game/types.ex b/lib/game/types.ex index 560920e..f96bf71 100644 --- a/lib/game/types.ex +++ b/lib/game/types.ex @@ -1,11 +1,5 @@ defmodule ExSpeedGame.Game.Types do @type pin :: Circuits.GPIO.pin_number() @type pins :: {pin(), pin(), pin(), pin()} - @type led_pins :: { - {pin(), pin()}, - {pin(), pin()}, - {pin(), pin()}, - {pin(), pin()} - } @type choice :: 1..4 end diff --git a/mix.exs b/mix.exs index ebef36c..fe11ed9 100644 --- a/mix.exs +++ b/mix.exs @@ -45,6 +45,7 @@ defmodule ExSpeedGame.MixProject do {:ring_logger, "~> 0.8"}, {:toolshed, "~> 0.2"}, {:circuits_gpio, "~> 0.4"}, + {:blinkchain, "~> 1.0"}, # Web UI stuff {:phoenix, "~> 1.5.1"}, diff --git a/mix.lock b/mix.lock index 84eaced..9c28a34 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,5 @@ %{ + "blinkchain": {:hex, :blinkchain, "1.0.0", "0bef4296c0fc3e36c153d717eefcaf5fa6681b4c81f3f5b98030e8044d3d3fe1", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "ecf950d7eb3fb0409efb64e952be3d921bc20045a5cd736cd4c4e52cfa852c7a"}, "circuits_gpio": {:hex, :circuits_gpio, "0.4.5", "4d5b0f707c425fc56f03086232259f65482a3d1f1cf15335253636d0bb846446", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "b42d28d60a6cfdfb6b21b66ab0b8c5de0ea5a32b390b61d2fe86a2ad8edb90ad"}, "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},