Change to using Blinkchain to control NeoPixels

This commit is contained in:
Mikko Ahlroth 2021-11-03 22:39:12 +02:00
parent 45f271aa3d
commit c709d616b9
6 changed files with 96 additions and 50 deletions

View file

@ -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

View file

@ -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},

View file

@ -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

View file

@ -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

View file

@ -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"},

View file

@ -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"},