diff --git a/.gitignore b/.gitignore index 5c2b50f..834641a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ erl_crash.dump .elixir_ls + +.env diff --git a/config/config.exs b/config/config.exs index 3bcf6bd..9266109 100644 --- a/config/config.exs +++ b/config/config.exs @@ -8,6 +8,7 @@ import Config button_pins = {24, 25, 5, 6} buttons = 4 leds_per_button = 7 +brightness = 45 config :ex_speed_game, target: Mix.target(), @@ -29,6 +30,12 @@ config :ex_speed_game, 3 => {250, 51, 51} }, + # Initial brightness of the LEDs + led_brightness: brightness, + + # LED PWM channel + led_channel: 0, + # 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, @@ -118,7 +125,7 @@ config :blinkchain, canvas: {buttons * leds_per_button, 1} config :blinkchain, :channel0, pin: 18, type: :grb, - brightness: 32, + brightness: brightness, gamma: gamma, arrangement: [ %{ diff --git a/lib/application.ex b/lib/application.ex index 4fcc855..b1a7022 100644 --- a/lib/application.ex +++ b/lib/application.ex @@ -32,6 +32,8 @@ defmodule ExSpeedGame.Application do def children(_target) do pins = Application.get_env(:ex_speed_game, :button_pins) debounce_delay = Application.get_env(:ex_speed_game, :debounce_delay) + led_channel = Application.get_env(:ex_speed_game, :led_channel) + led_brightness = Application.get_env(:ex_speed_game, :led_brightness) [ # Starts a worker by calling: ExSpeedGame.Worker.start_link(arg) @@ -39,7 +41,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{name: Lights}}, + {Lights, %Lights.Options{channel: led_channel, brightness: led_brightness, name: Lights}}, {Menu, %Menu.Options{name: Menu}}, {DynamicSupervisor, strategy: :one_for_one, name: GameSupervisor}, diff --git a/lib/game/button_input.ex b/lib/game/button_input.ex index 229d84b..b2d2546 100644 --- a/lib/game/button_input.ex +++ b/lib/game/button_input.ex @@ -55,7 +55,7 @@ defmodule ExSpeedGame.Game.ButtonInput do end def handle_call(:acquire, _from, %State{active: true, listener: l}) do - raise InputError, message: "ButtonInput already acquired to #{l}" + raise InputError, message: "ButtonInput already acquired to #{inspect(l)}" end # Release diff --git a/lib/game/change_brightness.ex b/lib/game/change_brightness.ex new file mode 100644 index 0000000..c138373 --- /dev/null +++ b/lib/game/change_brightness.ex @@ -0,0 +1,79 @@ +defmodule ExSpeedGame.Game.ChangeBrightness do + use GenServer, restart: :temporary + import ExSpeedGame.Utils.TypedStruct + require Logger + + alias ExSpeedGame.Game.Lights + alias ExSpeedGame.Game.ButtonInput + + @brightness_step 10 + + defmodule Options do + deftypedstruct(%{ + input_server: GenServer.name(), + lights_server: GenServer.name() + }) + end + + defmodule State do + deftypedstruct(%{ + input_server: GenServer.name(), + lights_server: GenServer.name(), + brightness: 0..255 + }) + end + + @spec start_link(Options.t()) :: :ignore | {:error, any} | {:ok, pid} + def start_link(%Options{} = opts) do + GenServer.start_link(__MODULE__, Map.from_struct(opts)) + end + + @impl true + @spec init(%{input_server: GenServer.name(), lights_server: GenServer.name()}) :: + {:ok, State.t()} + def init(%{input_server: input_server, lights_server: lights_server}) do + ButtonInput.acquire(input_server) + Lights.set(lights_server, [false, true, true, false]) + + brightness = Lights.get_brightness(lights_server) + + state = %State{ + input_server: input_server, + lights_server: lights_server, + brightness: brightness + } + + {:ok, state} + end + + @impl true + def handle_info(msg, state) + + def handle_info({:input, pin}, state) do + choice = ButtonInput.get_choice(pin, Application.get_env(:ex_speed_game, :button_pins)) + + case choice do + 2 -> + change_brightness(state, -@brightness_step) + + 3 -> + change_brightness(state, @brightness_step) + + _ -> + {:stop, :normal, state} + end + end + + @impl true + def terminate(_reason, state) do + ButtonInput.release(state.input_server) + end + + @spec change_brightness(State.t(), integer()) :: {:noreply, State.t()} + defp change_brightness(state, step) do + brightness = (state.brightness + step) |> max(0) |> min(255) + Lights.set_brightness(state.lights_server, brightness) + Logger.debug("Set light brightness to #{inspect(brightness)}") + {:noreply, %State{state | brightness: brightness}} + end +end diff --git a/lib/game/lights.ex b/lib/game/lights.ex index e7f1445..2b7afdb 100644 --- a/lib/game/lights.ex +++ b/lib/game/lights.ex @@ -12,12 +12,17 @@ defmodule ExSpeedGame.Game.Lights do defmodule Options do deftypedstruct(%{ - name: GenServer.name() + name: GenServer.name(), + channel: Blinkchain.channel_number(), + brightness: 0..255 }) end defmodule State do - deftypedstruct(%{}) + deftypedstruct(%{ + channel: Blinkchain.channel_number(), + brightness: 0..255 + }) end ### SERVER INTERFACE @@ -26,23 +31,22 @@ defmodule ExSpeedGame.Game.Lights do def start_link(%Options{} = opts) do GenServer.start_link( __MODULE__, - %{}, + Map.from_struct(opts), name: opts.name ) end @impl true - @spec init(any()) :: {:ok, State.t()} - def init(_) do - {:ok, %State{}} + @spec init(%{channel: Blinkchain.channel_number(), brightness: 0..255}) :: {:ok, State.t()} + def init(%{channel: channel, brightness: brightness}) do + Blinkchain.set_brightness(channel, brightness) + + {:ok, %State{channel: channel, brightness: brightness}} end @impl true - @spec handle_call( - {:set_lights, values()}, - GenServer.from(), - State.t() - ) :: {:reply, :ok, State.t()} + def handle_call(msg, from, state) + def handle_call({:set_lights, values}, _from, %State{} = state) do values |> Enum.with_index() @@ -65,20 +69,47 @@ defmodule ExSpeedGame.Game.Lights do {:reply, :ok, state} end + def handle_call({:set_brightness, brightness}, _from, %State{} = state) do + Blinkchain.set_brightness(state.channel, brightness) + Blinkchain.render() + + {:reply, :ok, %State{state | brightness: brightness}} + end + + def handle_call(:get_brightness, _from, %State{} = state) do + {:reply, state.brightness, state} + end + ### CLIENT INTERFACE @doc """ Set the lights on/off according to the given value. """ - @spec set(GenServer.name(), values()) :: :ok | no_return() + @spec set(GenServer.name(), values()) :: :ok def set(server, values) do :ok = GenServer.call(server, {:set_lights, values}) end + @doc """ + Set the brightness of the lights from 0 (off) to 255 (max). + """ + @spec set_brightness(GenServer.name(), 0..255) :: :ok + def set_brightness(server, brightness) do + :ok = GenServer.call(server, {:set_brightness, brightness}) + end + + @doc """ + Get the brightness of the lights from 0 (off) to 255 (max). + """ + @spec get_brightness(GenServer.name()) :: 0..255 + def get_brightness(server) do + GenServer.call(server, :get_brightness) + end + @doc """ Turn off all lights. """ - @spec clear(GenServer.name()) :: :ok | no_return() + @spec clear(GenServer.name()) :: :ok def clear(server) do set(server, [false, false, false, false]) end @@ -86,7 +117,7 @@ defmodule ExSpeedGame.Game.Lights do @doc """ Show the given number on the lights as a binary pattern. """ - @spec show_binary(GenServer.name(), integer()) :: :ok | no_return() + @spec show_binary(GenServer.name(), integer()) :: :ok def show_binary(server, number) do values = number2binary(number) set(server, values) @@ -95,7 +126,7 @@ defmodule ExSpeedGame.Game.Lights do @doc """ Set the given index on and the others off. """ - @spec set_index(GenServer.name(), integer()) :: :ok | no_return() + @spec set_index(GenServer.name(), integer()) :: :ok def set_index(server, index) do set(server, [index == 1, index == 2, index == 3, index == 4]) end @@ -119,7 +150,7 @@ defmodule ExSpeedGame.Game.Lights do end @spec index2color(0..3) :: {pos_integer(), pos_integer(), pos_integer()} - def index2color(index) do + defp index2color(index) do Map.fetch!(@led_colors, index) end end diff --git a/lib/game/menu.ex b/lib/game/menu.ex index e2557b1..8fb43a1 100644 --- a/lib/game/menu.ex +++ b/lib/game/menu.ex @@ -3,7 +3,7 @@ defmodule ExSpeedGame.Game.Menu do require Logger import ExSpeedGame.Utils.TypedStruct - alias ExSpeedGame.Game.{Types, ButtonInput, ShowIP, Lights} + alias ExSpeedGame.Game.{Types, ButtonInput, ShowIP, Lights, ChangeBrightness} alias ExSpeedGame.Game.Modes.{ Speed @@ -23,7 +23,9 @@ defmodule ExSpeedGame.Game.Menu do initial_delay: Application.get_env(:ex_speed_game, :delay_pro) }}, {"MemoryGame", Speed, %{}}, - {"Show IP", ShowIP, %{}} + {"Show IP", ShowIP, %{}}, + {"Change brightness", ChangeBrightness, + %ChangeBrightness.Options{input_server: ButtonInput, lights_server: Lights}} } @menu_size :erlang.tuple_size(@menu) @@ -129,6 +131,8 @@ defmodule ExSpeedGame.Game.Menu do {:noreply, new_state} end + def handle_info(_, state), do: {:noreply, state} + @impl true @spec handle_call({:esg_pubsub, :get_state}, GenServer.from(), State.t()) :: {:reply, State.t(), State.t()}