diff --git a/config/excalibur/config.exs b/config/excalibur/config.exs new file mode 100644 index 0000000..23cb864 --- /dev/null +++ b/config/excalibur/config.exs @@ -0,0 +1,101 @@ +import Config + +config :nerves, rpi_v2_ack: true + +# Use shoehorn to start the main application. See the shoehorn +# docs for separating out critical OTP applications such as those +# involved with firmware updates. + +config :shoehorn, + init: [:nerves_runtime, :nerves_pack], + app: Mix.Project.config()[:app] + +# Nerves Runtime can enumerate hardware devices and send notifications via +# SystemRegistry. This slows down startup and not many programs make use of +# this feature. + +config :nerves_runtime, :kernel, use_system_registry: false + +# Authorize the device to receive firmware using your public key. +# See https://hexdocs.pm/nerves_firmware_ssh/readme.html for more information +# on configuring nerves_firmware_ssh. + +keys = + [ + System.get_env("NERVES_SSH_PUB_KEY", ""), + Path.join([System.user_home!(), ".ssh", "id_rsa.pub"]), + Path.join([System.user_home!(), ".ssh", "id_ecdsa.pub"]), + Path.join([System.user_home!(), ".ssh", "id_ed25519.pub"]) + ] + |> Enum.filter(&File.exists?/1) + +if keys == [], + do: + IO.write(:stderr, """ + Warning: No SSH public key found. You will not be able to remotely + connect to the device to access the iex shell or updating firmware. + + If you have an SSH public key, you can set NERVES_SSH_PUB_KEY to + the path of the key. Otherwise, you can still flash the firmware + manually. + """) + +config :nerves_firmware_ssh, + authorized_keys: Enum.map(keys, &File.read!/1) + +# Configure the network using vintage_net +# See https://github.com/nerves-networking/vintage_net for more information +config :vintage_net, + regulatory_domain: "US", + config: [ + {"bond0", %{type: VintageNetDirect}} + ] + +config :mdns_lite, + # The `host` key specifies what hostnames mdns_lite advertises. `:hostname` + # advertises the device's hostname.local. For the official Nerves systems, this + # is "nerves-<4 digit serial#>.local". mdns_lite also advertises + # "nerves.local" for convenience. If more than one Nerves device is on the + # network, delete "nerves" from the list. + + host: [:hostname, "xebow"], + ttl: 120, + + # Advertise the following services over mDNS. + services: [ + %{ + name: "SSH Remote Login Protocol", + protocol: "ssh", + transport: "tcp", + port: 22 + }, + %{ + name: "Secure File Transfer Protocol over SSH", + protocol: "sftp-ssh", + transport: "tcp", + port: 22 + }, + %{ + name: "Erlang Port Mapper Daemon", + protocol: "epmd", + transport: "tcp", + port: 4369 + } + ] + +# Use Ringlogger as the logger backend and remove :console. +# See https://hexdocs.pm/ring_logger/readme.html for more information on +# configuring ring_logger. + +config :logger, backends: [RingLogger] + +# Phoenix config: +# Configures the endpoint +config :xebow, XebowWeb.Endpoint, + http: [port: 80, ip: {0, 0, 0, 0}], + url: [host: "xebow.local", port: 80], + code_reloader: false + +config :xebow, settings_path: "/root/settings" + +import_config "#{Mix.env()}.exs" diff --git a/config/excalibur/dev.exs b/config/excalibur/dev.exs new file mode 100644 index 0000000..becde76 --- /dev/null +++ b/config/excalibur/dev.exs @@ -0,0 +1 @@ +import Config diff --git a/lib/rgb_matrix/animation/pinwheel.ex b/lib/rgb_matrix/animation/pinwheel.ex index ee745df..989fb6e 100644 --- a/lib/rgb_matrix/animation/pinwheel.ex +++ b/lib/rgb_matrix/animation/pinwheel.ex @@ -22,6 +22,8 @@ defmodule RGBMatrix.Animation.Pinwheel do %State{tick: 0, speed: 100, leds: leds, center: determine_center(leds)} end + defp determine_center([]), do: %{x: 0, y: 0} + defp determine_center(leds) do {%{x: min_x}, %{x: max_x}} = Enum.min_max_by(leds, & &1.x) {%{y: min_y}, %{y: max_y}} = Enum.min_max_by(leds, & &1.y) diff --git a/lib/xebow.ex b/lib/xebow.ex index 3825ce0..109bae7 100644 --- a/lib/xebow.ex +++ b/lib/xebow.ex @@ -4,46 +4,17 @@ defmodule Xebow do Keybow kit. """ - alias Layout.{Key, LED} alias RGBMatrix.{Animation, Engine} - alias Xebow.Settings + alias Xebow.{Excalibur, Keybow, Settings} require Logger - @leds [ - LED.new(:l001, 0, 0), - LED.new(:l002, 1, 0), - LED.new(:l003, 2, 0), - LED.new(:l004, 0, 1), - LED.new(:l005, 1, 1), - LED.new(:l006, 2, 1), - LED.new(:l007, 0, 2), - LED.new(:l008, 1, 2), - LED.new(:l009, 2, 2), - LED.new(:l010, 0, 3), - LED.new(:l011, 1, 3), - LED.new(:l012, 2, 3) - ] - - @keys [ - Key.new(:k001, 0, 0, led: :l001), - Key.new(:k002, 1, 0, led: :l002), - Key.new(:k003, 2, 0, led: :l003), - Key.new(:k004, 0, 1, led: :l004), - Key.new(:k005, 1, 1, led: :l005), - Key.new(:k006, 2, 1, led: :l006), - Key.new(:k007, 0, 2, led: :l007), - Key.new(:k008, 1, 2, led: :l008), - Key.new(:k009, 2, 2, led: :l009), - Key.new(:k010, 0, 3, led: :l010), - Key.new(:k011, 1, 3, led: :l011), - Key.new(:k012, 2, 3, led: :l012) - ] - - @layout Layout.new(@keys, @leds) - - @spec layout() :: Layout.t() - def layout, do: @layout + @layout_host Layout.new([]) + + @spec layout(atom) :: Layout.t() + def layout(:host), do: @layout_host + def layout(:keybow), do: Keybow.Layout.layout() + def layout(:excalibur), do: Excalibur.Layout.layout() @type animations :: [Animation.t()] @type animation_params :: %{String.t() => atom | number | String.t()} @@ -238,7 +209,10 @@ defmodule Xebow do end defp initialize_animation(animation_type) do - Animation.new(animation_type, @leds) + target = Xebow.Application.target() + layout = layout(target) + leds = Layout.leds(layout) + Animation.new(animation_type, leds) end defp update_state_with_animation_types(state, animation_types) do diff --git a/lib/xebow/application.ex b/lib/xebow/application.ex index f12453b..a17c09c 100644 --- a/lib/xebow/application.ex +++ b/lib/xebow/application.ex @@ -7,7 +7,7 @@ defmodule Xebow.Application do alias Xebow.Settings - @leds Xebow.layout() |> Layout.leds() + @leds Xebow.layout(Mix.target()) |> Layout.leds() if Mix.target() == :host do defp maybe_validate_firmware, @@ -58,14 +58,21 @@ defmodule Xebow.Application do ] end - def children(_target) do + def children(:keybow) do [ # Children for all targets except host # Starts a worker by calling: Xebow.Worker.start_link(arg) # {Xebow.Worker, arg}, Xebow.HIDGadget, - Xebow.LEDs, - Xebow.Keyboard + Xebow.Keybow.LEDs, + Xebow.Keybow.Keyboard + ] + end + + def children(:excalibur) do + [ + Xebow.HIDGadget, + Xebow.Excalibur.Keyboard ] end diff --git a/lib/xebow/excalibur/keyboard.ex b/lib/xebow/excalibur/keyboard.ex new file mode 100644 index 0000000..3eda938 --- /dev/null +++ b/lib/xebow/excalibur/keyboard.ex @@ -0,0 +1,284 @@ +require Logger + +defmodule Xebow.Excalibur.Keyboard do + @moduledoc """ + Keyboard matrix scanning for Excalibur. + """ + + use GenServer + + alias Circuits.GPIO + alias Xebow.Excalibur.Utils + + @matrix_layout [ + [:k001, :k002, :k003, :k004, :k005, :k006, :k007, :k008, :k009], + [:k010, :k011, :k012, :k013, :k014, :k015, :k016, :k017, :k018], + [:k019, :k020, :k021, :k022, :k023, :k024, :k025, :k026, :k027], + [:k028, :k029, :k030, :k031, :k032, :k033, :k034, :k035, :k036], + [:k037, :k038, :k039, :k040, :k041, :k042, :k043, :k044, :k045], + [:k046, :k047, :k048, :k049, :k050, :k051, :k052, :k053, :k054], + [:k055, :k056, :k057, :k058, :k059, :k060, :k061, :k062, :k063], + [:k064, :k065, :k066, :k067, :k068] + ] + + @row_pin [21, 20, 14, 15, 18, 27, 23, 24] + @col_pins [10, 25, 8, 7, 1, 12, 16, 3, 2] + + @debounce_window 10 + + # this file exists because `Xebow.HIDGadget` set it up during boot. + @hid_device "/dev/hidg0" + + @keymap [ + # Layer 0: + %{ + k001: AFK.Keycode.Key.new(:escape), + k002: AFK.Keycode.Key.new(:"1"), + k003: AFK.Keycode.Key.new(:"2"), + k004: AFK.Keycode.Key.new(:"3"), + k005: AFK.Keycode.Key.new(:"4"), + k006: AFK.Keycode.Key.new(:"5"), + k007: AFK.Keycode.Key.new(:"6"), + k008: AFK.Keycode.Key.new(:"7"), + k009: AFK.Keycode.Key.new(:"8"), + k010: AFK.Keycode.Key.new(:"9"), + k011: AFK.Keycode.Key.new(:"0"), + k012: AFK.Keycode.Key.new(:minus), + k013: AFK.Keycode.Key.new(:equals), + k014: AFK.Keycode.Key.new(:backspace), + k015: AFK.Keycode.Key.new(:home), + k016: AFK.Keycode.Key.new(:page_up), + # + k017: AFK.Keycode.Key.new(:tab), + k018: AFK.Keycode.Key.new(:q), + k019: AFK.Keycode.Key.new(:w), + k020: AFK.Keycode.Key.new(:e), + k021: AFK.Keycode.Key.new(:r), + k022: AFK.Keycode.Key.new(:t), + k023: AFK.Keycode.Key.new(:y), + k024: AFK.Keycode.Key.new(:u), + k025: AFK.Keycode.Key.new(:i), + k026: AFK.Keycode.Key.new(:o), + k027: AFK.Keycode.Key.new(:p), + k028: AFK.Keycode.Key.new(:left_square_bracket), + k029: AFK.Keycode.Key.new(:right_square_bracket), + k030: AFK.Keycode.Key.new(:backslash), + k031: AFK.Keycode.Key.new(:end), + k032: AFK.Keycode.Key.new(:page_down), + # + k033: AFK.Keycode.Key.new(:caps_lock), + k034: AFK.Keycode.Key.new(:a), + k035: AFK.Keycode.Key.new(:s), + k036: AFK.Keycode.Key.new(:d), + k037: AFK.Keycode.Key.new(:f), + k038: AFK.Keycode.Key.new(:g), + k039: AFK.Keycode.Key.new(:h), + k040: AFK.Keycode.Key.new(:j), + k041: AFK.Keycode.Key.new(:k), + k042: AFK.Keycode.Key.new(:l), + k043: AFK.Keycode.Key.new(:semicolon), + k044: AFK.Keycode.Key.new(:single_quote), + k045: AFK.Keycode.Key.new(:enter), + # + k046: AFK.Keycode.Modifier.new(:left_shift), + k047: AFK.Keycode.Key.new(:z), + k048: AFK.Keycode.Key.new(:x), + k049: AFK.Keycode.Key.new(:c), + k050: AFK.Keycode.Key.new(:v), + k051: AFK.Keycode.Key.new(:b), + k052: AFK.Keycode.Key.new(:n), + k053: AFK.Keycode.Key.new(:m), + k054: AFK.Keycode.Key.new(:comma), + k055: AFK.Keycode.Key.new(:period), + k056: AFK.Keycode.Key.new(:slash), + k057: AFK.Keycode.Modifier.new(:right_shift), + k058: AFK.Keycode.Key.new(:up), + # + k059: AFK.Keycode.Modifier.new(:left_control), + k060: AFK.Keycode.Modifier.new(:left_super), + k061: AFK.Keycode.Modifier.new(:left_alt), + k062: AFK.Keycode.Key.new(:space), + k063: AFK.Keycode.Modifier.new(:right_alt), + k064: AFK.Keycode.Layer.new(:hold, 1), + k065: AFK.Keycode.Modifier.new(:right_control), + k066: AFK.Keycode.Key.new(:left), + k067: AFK.Keycode.Key.new(:down), + k068: AFK.Keycode.Key.new(:right) + }, + # Layer 0: + %{ + k001: AFK.Keycode.Key.new(:grave), + k031: AFK.Keycode.Key.new(:delete) + } + ] + + # Client + + def start_link([]) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + def reset! do + GenServer.call(__MODULE__, :reset!) + end + + # Server + + @impl true + def init([]) do + {:ok, keyboard_state} = + AFK.State.start_link( + keymap: @keymap, + event_receiver: self(), + hid_report_mod: AFK.HIDReport.SixKeyRollover + ) + + state = %{ + buffer: [], + held_keys: [], + matrix_config: init_matrix_config(), + timer: nil, + hid: File.open!(@hid_device, [:write]), + keyboard_state: keyboard_state + } + + send(self(), :scan) + + {:ok, state} + end + + @impl true + def handle_call(:reset!, _from, state) do + case state.timer do + nil -> :ok + timer -> Process.cancel_timer(timer) + end + + state = %{state | buffer: [], held_keys: [], timer: nil} + + {:reply, :ok, state} + end + + defp init_matrix_config do + # transpose matrix, because we need to scan by column, not by row + matrix_layout = + @matrix_layout + |> Utils.pad_matrix() + |> Enum.zip() + |> Enum.map(fn col -> + col + |> Tuple.to_list() + |> Enum.filter(& &1) + end) + + # open the pins + row_pins = @row_pin |> Enum.map(&open_input_pin!/1) + col_pins = @col_pins |> Enum.map(&open_output_pin!/1) + + # zip the open pin resources into the matrix structure + Enum.zip( + col_pins, + Enum.map(matrix_layout, fn col -> + Enum.zip(row_pins, col) + end) + ) + end + + defp open_input_pin!(pin_number) do + # clear any stuck pins + {:ok, pin} = GPIO.open(pin_number, :output, initial_value: 0) + :ok = GPIO.write(pin, 0) + :ok = GPIO.close(pin) + + {:ok, pin} = GPIO.open(pin_number, :input, pull_mode: :pulldown, initial_value: 0) + pin + end + + defp open_output_pin!(pin_number) do + {:ok, pin} = GPIO.open(pin_number, :output, initial_value: 0) + pin + end + + @impl true + def handle_info(:flush, state) do + state.buffer + |> Enum.reverse() + |> Utils.dedupe_events() + |> Enum.each(fn + {:pressed, key_id} -> + Logger.debug(fn -> "Key pressed: #{key_id}" end) + AFK.State.press_key(state.keyboard_state, key_id) + + {:released, key_id} -> + Logger.debug(fn -> "Key released: #{key_id}" end) + AFK.State.release_key(state.keyboard_state, key_id) + end) + + {:noreply, %{state | buffer: [], timer: nil}} + end + + @impl true + def handle_info(:scan, state) do + keys = scan(state.matrix_config) + + released = state.held_keys -- keys + pressed = keys -- state.held_keys + + buffer = Enum.reduce(released, state.buffer, fn key, acc -> [{:released, key} | acc] end) + buffer = Enum.reduce(pressed, buffer, fn key, acc -> [{:pressed, key} | acc] end) + + state = + if buffer != state.buffer do + set_debounce_timer(state) + else + state + end + + Process.send_after(self(), :scan, 2) + + {:noreply, %{state | held_keys: keys, buffer: buffer}} + end + + @impl GenServer + def handle_info({:hid_report, hid_report}, state) do + IO.binwrite(state.hid, hid_report) + {:noreply, state} + end + + defp scan(matrix_config) do + Enum.reduce(matrix_config, [], &scan_col/2) + end + + defp scan_col({col_pin, rows}, acc) do + with_pin_high(col_pin, fn -> + Enum.reduce(rows, acc, &scan_row/2) + end) + end + + defp scan_row({row_pin, key_id}, acc) do + case pin_high?(row_pin) do + true -> [key_id | acc] + false -> acc + end + end + + defp with_pin_high(pin, fun) do + :ok = GPIO.write(pin, 1) + response = fun.() + :ok = GPIO.write(pin, 0) + response + end + + defp pin_high?(pin) do + GPIO.read(pin) == 1 + end + + defp set_debounce_timer(%{timer: nil} = state) do + %{state | timer: Process.send_after(self(), :flush, @debounce_window)} + end + + defp set_debounce_timer(%{timer: timer} = state) do + Process.cancel_timer(timer) + set_debounce_timer(%{state | timer: nil}) + end +end diff --git a/lib/xebow/excalibur/layout.ex b/lib/xebow/excalibur/layout.ex new file mode 100644 index 0000000..1685ad1 --- /dev/null +++ b/lib/xebow/excalibur/layout.ex @@ -0,0 +1,87 @@ +defmodule Xebow.Excalibur.Layout do + @moduledoc """ + Defines the physical layout of the Excalibur keyboard. + """ + + alias Layout.Key + + @keys [ + Key.new(:k001, 0, 0), + Key.new(:k002, 1, 0), + Key.new(:k003, 2, 0), + Key.new(:k004, 3, 0), + Key.new(:k005, 4, 0), + Key.new(:k006, 5, 0), + Key.new(:k007, 6, 0), + Key.new(:k008, 7, 0), + Key.new(:k009, 8, 0), + Key.new(:k010, 9, 0), + Key.new(:k011, 10, 0), + Key.new(:k012, 11, 0), + Key.new(:k013, 12, 0), + Key.new(:k014, 13.5, 0, width: 2), + Key.new(:k015, 15.25, 0), + Key.new(:k016, 16.25, 0), + # + Key.new(:k017, 0.25, 1, width: 1.5), + Key.new(:k018, 1.5, 1), + Key.new(:k019, 2.5, 1), + Key.new(:k020, 3.5, 1), + Key.new(:k021, 4.5, 1), + Key.new(:k022, 5.5, 1), + Key.new(:k023, 6.5, 1), + Key.new(:k024, 7.5, 1), + Key.new(:k025, 8.5, 1), + Key.new(:k026, 9.5, 1), + Key.new(:k027, 10.5, 1), + Key.new(:k028, 11.5, 1), + Key.new(:k029, 12.5, 1), + Key.new(:k030, 13.75, 1, width: 1.5), + Key.new(:k031, 15.25, 1), + Key.new(:k032, 16.25, 1), + # + Key.new(:k033, 0.375, 2, width: 1.75), + Key.new(:k034, 1.75, 2), + Key.new(:k035, 2.75, 2), + Key.new(:k036, 3.75, 2), + Key.new(:k037, 4.75, 2), + Key.new(:k038, 5.75, 2), + Key.new(:k039, 6.75, 2), + Key.new(:k040, 7.75, 2), + Key.new(:k041, 8.75, 2), + Key.new(:k042, 9.75, 2), + Key.new(:k043, 10.75, 2), + Key.new(:k044, 11.75, 2), + Key.new(:k045, 13.375, 2, width: 2.25), + # + Key.new(:k046, 0.625, 3, width: 2.25), + Key.new(:k047, 2.25, 3), + Key.new(:k048, 3.25, 3), + Key.new(:k049, 4.25, 3), + Key.new(:k050, 5.25, 3), + Key.new(:k051, 6.25, 3), + Key.new(:k052, 7.25, 3), + Key.new(:k053, 8.25, 3), + Key.new(:k054, 9.25, 3), + Key.new(:k055, 10.25, 3), + Key.new(:k056, 11.25, 3), + Key.new(:k057, 13.125, 3, width: 2.75), + Key.new(:k058, 15.25, 3), + # + Key.new(:k059, 0.125, 4, width: 1.25), + Key.new(:k060, 1.375, 4, width: 1.25), + Key.new(:k061, 2.625, 4, width: 1.25), + Key.new(:k062, 6.375, 4, width: 6.25), + Key.new(:k063, 10.125, 4, width: 1.25), + Key.new(:k064, 11.375, 4, width: 1.25), + Key.new(:k065, 12.625, 4, width: 1.25), + Key.new(:k066, 14.25, 4), + Key.new(:k067, 15.25, 4), + Key.new(:k068, 16.25, 4) + ] + + @layout Layout.new(@keys) + + @spec layout() :: Layout.t() + def layout, do: @layout +end diff --git a/lib/xebow/excalibur/utils.ex b/lib/xebow/excalibur/utils.ex new file mode 100644 index 0000000..80a996a --- /dev/null +++ b/lib/xebow/excalibur/utils.ex @@ -0,0 +1,65 @@ +defmodule Xebow.Excalibur.Utils do + @moduledoc false + + @doc """ + Pads a matrix that isn't fully complete with `nil`s. + Assumes the first row is complete, and all subsequent rows are either complete + or shorter than the first. + ## Examples + iex> pad_matrix([[1, 2, 3], [4, 5, 6], [7]]) + [[1, 2, 3], [4, 5, 6], [7, nil, nil]] + iex> pad_matrix([[1, 2, 3], [4, 5], [6]]) + [[1, 2, 3], [4, 5, nil], [6, nil, nil]] + """ + def pad_matrix([first | _rest] = matrix) do + length = Enum.count(first) + + Enum.map(matrix, fn row -> + if Enum.count(row) != length do + [[], padded] = + Enum.reduce(1..length, [row, []], fn + _, [[], acc] -> [[], [nil | acc]] + _, [[next | rest], acc] -> [rest, [next | acc]] + end) + + Enum.reverse(padded) + else + row + end + end) + end + + @doc """ + Dedupes a list of key press and release events. + ## Examples + iex> dedupe_events([pressed: :k001, released: :k001]) + [] + iex> dedupe_events([released: :k001, pressed: :k001]) + [] + iex> dedupe_events([pressed: :k001, released: :k001, pressed: :k001]) + [pressed: :k001] + iex> dedupe_events([released: :k001, pressed: :k001, released: :k001]) + [released: :k001] + iex> dedupe_events([pressed: :k001, pressed: :k002, released: :k001]) + [pressed: :k002] + iex> dedupe_events([pressed: :k001, pressed: :k002, released: :k002, released: :k001]) + [] + iex> dedupe_events([released: :k002, released: :k001, pressed: :k002, pressed: :k001]) + [] + """ + def dedupe_events(events) do + events + |> Enum.reduce([], fn {type, key}, acc -> + opposite = opposite(type) + + case Keyword.get(acc, key) do + nil -> [{key, type} | acc] + ^opposite -> Keyword.delete(acc, key) + end + end) + |> Enum.reduce([], fn {x, y}, acc -> [{y, x} | acc] end) + end + + defp opposite(:pressed), do: :released + defp opposite(:released), do: :pressed +end diff --git a/lib/xebow/keyboard.ex b/lib/xebow/keybow/keyboard.ex similarity index 98% rename from lib/xebow/keyboard.ex rename to lib/xebow/keybow/keyboard.ex index fa93aa1..1a7c3f2 100644 --- a/lib/xebow/keyboard.ex +++ b/lib/xebow/keybow/keyboard.ex @@ -1,6 +1,6 @@ require Logger -defmodule Xebow.Keyboard do +defmodule Xebow.Keybow.Keyboard do @moduledoc """ Keyboard GenServer for handling key events. @@ -185,7 +185,7 @@ defmodule Xebow.Keyboard do end defp rgb_matrix_interact(key_id) do - case Layout.led_for_key(Xebow.layout(), key_id) do + case Layout.led_for_key(Xebow.layout(:keybow), key_id) do nil -> :noop led -> Engine.interact(led) end diff --git a/lib/xebow/keybow/layout.ex b/lib/xebow/keybow/layout.ex new file mode 100644 index 0000000..c844e80 --- /dev/null +++ b/lib/xebow/keybow/layout.ex @@ -0,0 +1,42 @@ +defmodule Xebow.Keybow.Layout do + @moduledoc """ + Describes the physical layout of the Keybow Kit. + """ + + alias Layout.{Key, LED} + + @leds [ + LED.new(:l001, 0, 0), + LED.new(:l002, 1, 0), + LED.new(:l003, 2, 0), + LED.new(:l004, 0, 1), + LED.new(:l005, 1, 1), + LED.new(:l006, 2, 1), + LED.new(:l007, 0, 2), + LED.new(:l008, 1, 2), + LED.new(:l009, 2, 2), + LED.new(:l010, 0, 3), + LED.new(:l011, 1, 3), + LED.new(:l012, 2, 3) + ] + + @keys [ + Key.new(:k001, 0, 0, led: :l001), + Key.new(:k002, 1, 0, led: :l002), + Key.new(:k003, 2, 0, led: :l003), + Key.new(:k004, 0, 1, led: :l004), + Key.new(:k005, 1, 1, led: :l005), + Key.new(:k006, 2, 1, led: :l006), + Key.new(:k007, 0, 2, led: :l007), + Key.new(:k008, 1, 2, led: :l008), + Key.new(:k009, 2, 2, led: :l009), + Key.new(:k010, 0, 3, led: :l010), + Key.new(:k011, 1, 3, led: :l011), + Key.new(:k012, 2, 3, led: :l012) + ] + + @layout Layout.new(@keys, @leds) + + @spec layout() :: Layout.t() + def layout, do: @layout +end diff --git a/lib/xebow/leds.ex b/lib/xebow/keybow/leds.ex similarity index 94% rename from lib/xebow/leds.ex rename to lib/xebow/keybow/leds.ex index 1c39abc..644f0c6 100644 --- a/lib/xebow/leds.ex +++ b/lib/xebow/keybow/leds.ex @@ -1,4 +1,4 @@ -defmodule Xebow.LEDs do +defmodule Xebow.Keybow.LEDs do @moduledoc """ GenServer that interacts with the SPI device that controls the RGB LEDs on the Keybow. @@ -25,7 +25,7 @@ defmodule Xebow.LEDs do @sof <<0, 0, 0, 0>> @eof <<255, 255, 255, 255>> # This is the hardware order that the LED colors need to be sent to the SPI - # device in. The LED IDs are the ones from `Xebow.layout/0`. + # device in. The LED IDs are the ones from `Xebow.layout/1` given arg :keybow. @spi_led_order [ :l001, :l004, diff --git a/lib/xebow_web/live/matrix_live.ex b/lib/xebow_web/live/matrix_live.ex index 440c1ef..33871bd 100644 --- a/lib/xebow_web/live/matrix_live.ex +++ b/lib/xebow_web/live/matrix_live.ex @@ -5,7 +5,7 @@ defmodule XebowWeb.MatrixLive do alias RGBMatrix.Engine - @layout Xebow.layout() + @layout Xebow.layout(Mix.target()) @impl Phoenix.LiveView def mount(_params, _session, socket) do diff --git a/mix.exs b/mix.exs index 4bf2458..c661597 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule Xebow.MixProject do @app :xebow @version "0.1.0" - @all_targets [:keybow] + @all_targets [:keybow, :excalibur] def project do [ @@ -23,10 +23,7 @@ defmodule Xebow.MixProject do run: :host, test: :host, dialyzer: :keybow, - docs: :keybow, - firmware: :keybow, - "firmware.upload": :keybow, - upload: :keybow + docs: :keybow ], dialyzer: [ ignore_warnings: "dialyzer.ignore.exs", @@ -113,7 +110,7 @@ defmodule Xebow.MixProject do github: "ElixirSeattle/nerves_system_keybow", ref: "v2.0.0-rc.0+keybow.1", runtime: false, - targets: :keybow} + targets: [:keybow, :excalibur]} ] end diff --git a/test/xebow_test.exs b/test/xebow_test.exs index be50f5a..a7cbc12 100644 --- a/test/xebow_test.exs +++ b/test/xebow_test.exs @@ -17,8 +17,16 @@ defmodule XebowTest do end end - test "has layout" do - assert %Layout{} = Xebow.layout() + test "has a layout for the keybow" do + assert %Layout{} = Xebow.layout(:keybow) + end + + test "has a layout for excalibur" do + assert %Layout{} = Xebow.layout(:excalibur) + end + + test "has a layout for host" do + assert %Layout{} = Xebow.layout(:host) end test "can get and set active animation types" do