Initial commit

This commit is contained in:
Mikko Ahlroth 2020-03-08 22:14:51 +02:00
commit e5af29f06c
18 changed files with 686 additions and 0 deletions

4
.formatter.exs Normal file
View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

19
.gitignore vendored Normal file
View file

@ -0,0 +1,19 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
.elixir_ls

2
.tool-versions Normal file
View file

@ -0,0 +1,2 @@
elixir 1.10.2-otp-22
erlang 22.2.8

32
README.md Normal file
View file

@ -0,0 +1,32 @@
# ExSpeedGame
**TODO: Add description**
## Targets
Nerves applications produce images for hardware targets based on the
`MIX_TARGET` environment variable. If `MIX_TARGET` is unset, `mix` builds an
image that runs on the host (e.g., your laptop). This is useful for executing
logic tests, running utilities, and debugging. Other targets are represented by
a short name like `rpi3` that maps to a Nerves system image for that platform.
All of this logic is in the generated `mix.exs` and may be customized. For more
information about targets see:
https://hexdocs.pm/nerves/targets.html#content
## Getting Started
To start your Nerves app:
* `export MIX_TARGET=my_target` or prefix every command with
`MIX_TARGET=my_target`. For example, `MIX_TARGET=rpi3`
* Install dependencies with `mix deps.get`
* Create firmware with `mix firmware`
* Burn to an SD card with `mix firmware.burn`
## Learn more
* Official docs: https://hexdocs.pm/nerves/getting-started.html
* Official website: https://nerves-project.org/
* Forum: https://elixirforum.com/c/nerves-forum
* Discussion Slack elixir-lang #nerves ([Invite](https://elixir-slackin.herokuapp.com/))
* Source: https://github.com/nerves-project/nerves

52
config/config.exs Normal file
View file

@ -0,0 +1,52 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
import Config
config :ex_speed_game,
target: Mix.target(),
# Set to true to enable debug prints (via serial console) and false to disable.
debug: true,
# Amount of buttons in the game.
buttons: 4,
# 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: [17, 27, 22, 23],
# Pins of the LED lights of the buttons.
button_light_pins: [],
# 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,
# Delay at start of game between beeps, in milliseconds.
delay_start: 570,
# Maximum amount of beeps you can be "behind" before the game is stopped.
max_waiting: 20
# Customize non-Elixir parts of the firmware. See
# https://hexdocs.pm/nerves/advanced-configuration.html for details.
config :nerves, :firmware, rootfs_overlay: "rootfs_overlay"
# Set the SOURCE_DATE_EPOCH date for reproducible builds.
# See https://reproducible-builds.org/docs/source-date-epoch/ for more information
config :nerves, source_date_epoch: "1583172807"
# 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]
if Mix.target() != :host do
import_config "target.exs"
end

58
config/target.exs Normal file
View file

@ -0,0 +1,58 @@
import Config
# 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_init_gadget],
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 =
[
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:
Mix.raise("""
No SSH public keys found in ~/.ssh. An ssh authorized key is needed to
log into the Nerves device and update firmware on it using ssh.
See your project's config.exs for this error message.
""")
config :nerves_firmware_ssh,
authorized_keys: Enum.map(keys, &File.read!/1)
# Configure nerves_init_gadget.
# See https://hexdocs.pm/nerves_init_gadget/readme.html for more information.
# Setting the node_name will enable Erlang Distribution.
# Only enable this for prod if you understand the risks.
node_name = if Mix.env() != :prod, do: "ex_speed_game"
config :nerves_init_gadget,
ifname: "usb0",
address_method: :dhcpd,
mdns_domain: "nerves.local",
node_name: node_name,
node_host: :mdns_domain
# Import target specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
# Uncomment to use target specific configurations
# import_config "#{Mix.target()}.exs"

48
lib/application.ex Normal file
View file

@ -0,0 +1,48 @@
defmodule ExSpeedGame.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: ExSpeedGame.Supervisor]
children =
[
# Children for all targets
# Starts a worker by calling: ExSpeedGame.Worker.start_link(arg)
# {ExSpeedGame.Worker, arg},
] ++ children(target())
Supervisor.start_link(children, opts)
end
# List all child processes to be supervised
def children(:host) do
[
# Children that only run on the host
# Starts a worker by calling: ExSpeedGame.Worker.start_link(arg)
# {ExSpeedGame.Worker, arg},
]
end
def children(_target) do
pins = Application.get_env(:ex_speed_game, :button_pins)
debounce_delay = Application.get_env(:ex_speed_game, :debounce_delay)
[
# Children for all targets except host
# Starts a worker by calling: ExSpeedGame.Worker.start_link(arg)
{ExSpeedGame.Game.ButtonInput,
{{pins, debounce_delay}, name: ExSpeedGame.Game.ButtonInput}},
{ExSpeedGame.Test, []}
]
end
def target() do
Application.get_env(:ex_speed_game, :target)
end
end

135
lib/game/button_input.ex Normal file
View file

@ -0,0 +1,135 @@
defmodule ExSpeedGame.Game.ButtonInput do
use GenServer
alias ExSpeedGame.Game.Types
require Logger
defmodule State do
@type t :: %__MODULE__{
pins: Types.pins(),
debounce_delay: integer(),
listener: pid() | nil,
pin_refs: [reference()],
pin_timers: %{optional(Types.pin()) => reference()},
active: boolean()
}
@enforce_keys [:pins, :debounce_delay]
defstruct [:pins, :debounce_delay, :listener, pin_refs: [], pin_timers: %{}, active: false]
end
defmodule InputError do
defexception [:message]
end
### SERVER INTERFACE
@spec start_link({{[Types.pins()], integer()}, keyword()}) ::
:ignore | {:error, any} | {:ok, pid}
def start_link({init, opts}) do
GenServer.start_link(__MODULE__, init, opts)
end
@impl true
@spec init({Types.pins(), integer()}) :: {:ok, State.t()}
def init({pins, debounce_delay}) do
{:ok, %State{pins: pins, debounce_delay: debounce_delay}}
end
@impl true
def handle_call(msg, from, state)
# Acquire
@spec handle_call(:acquire, GenServer.from(), %State{active: false}) :: {:reply, :ok, State.t()}
def handle_call(:acquire, {from, _}, %State{active: false} = state) do
pin_refs = for pin <- state.pins, do: init_pin(pin)
{:reply, :ok, %State{state | listener: from, pin_refs: pin_refs, active: true}}
end
@spec handle_call(:acquire, any, %State{active: true}) :: no_return()
def handle_call(:acquire, _from, %State{active: true, listener: l}) do
raise InputError, message: "ButtonInput already acquired to #{l}"
end
# Release
@spec handle_call(:release, any, %State{active: true}) :: {:reply, :ok, State.t()}
def handle_call(:release, _from, %State{active: true} = state) do
# Circuits.GPIO will free resources automatically when the pin refs are GCd
{:reply, :ok, %State{state | listener: nil, pin_refs: %{}}}
end
@spec handle_call(:release, any, %State{active: false}) :: no_return()
def handle_call(:release, _from, %State{active: false}) do
raise InputError, message: "ButtonInput already released"
end
@impl true
def handle_info(msg, state)
@spec handle_info({:circuits_gpio, Types.pin(), integer(), 0 | 1}, %State{active: true}) ::
{:noreply, State.t()}
def handle_info({:circuits_gpio, pin, _time, value} = msg, %State{active: true} = state) do
Logger.debug(inspect(msg))
timer_ref = Map.get(state.pin_timers, pin)
if not is_nil(timer_ref) do
Process.cancel_timer(timer_ref)
end
pin_timers =
case value do
0 ->
Map.delete(state.pin_timers, pin)
1 ->
ref = Process.send_after(self(), {:debounce, pin}, state.debounce_delay)
Map.put(state.pin_timers, pin, ref)
end
{:noreply, %State{state | pin_timers: pin_timers}}
end
@spec handle_info({:debounce, Types.pin()}, %State{active: true}) :: {:noreply, State.t()}
def handle_info({:debounce, pin}, %State{active: true} = state) do
send(state.listener, {:input, pin})
pin_timers = Map.delete(state.pin_timers, pin)
{:noreply, %State{state | pin_timers: pin_timers}}
end
# Discard all other messages
@spec handle_info(any, %State{active: false}) :: {:noreply, State.t()}
def handle_info(_msg, %State{active: false} = state) do
{:noreply, state}
end
@spec init_pin(Types.pin()) :: reference()
defp init_pin(pin) do
{:ok, ref} = Circuits.GPIO.open(pin, :input, pull_mode: :pulldown)
:ok = Circuits.GPIO.set_interrupts(ref, :both)
ref
end
### CLIENT INTERFACE
@doc """
Acquire the button input for current process and start monitoring the buttons.
When this has been called, the ButtonInput process will start sending messages for button events.
"""
@spec acquire(GenServer.server()) :: term
def acquire(server) do
GenServer.call(server, :acquire)
end
@doc """
Release the button input and stop monitoring the buttons.
"""
@spec release(GenServer.server()) :: term
def release(server) do
GenServer.call(server, :release)
end
end

0
lib/game/manager.ex Normal file
View file

5
lib/game/types.ex Normal file
View file

@ -0,0 +1,5 @@
defmodule ExSpeedGame.Game.Types do
@type pin :: Circuits.GPIO.pin_number()
@type pins :: [pin]
@type led_pins :: [pin]
end

20
lib/test.ex Normal file
View file

@ -0,0 +1,20 @@
defmodule ExSpeedGame.Test do
use GenServer
require Logger
def start_link(opts) do
GenServer.start_link(__MODULE__, opts)
end
@impl true
def init(_) do
ExSpeedGame.Game.ButtonInput.acquire(ExSpeedGame.Game.ButtonInput)
{:ok, :ok}
end
@impl true
def handle_info(msg, s) do
Logger.debug(inspect(msg))
{:noreply, s}
end
end

66
mix.exs Normal file
View file

@ -0,0 +1,66 @@
defmodule ExSpeedGame.MixProject do
use Mix.Project
@app :ex_speed_game
@version "0.1.0"
@all_targets [:rpi0]
def project do
[
app: @app,
version: @version,
elixir: "~> 1.10",
archives: [nerves_bootstrap: "~> 1.7"],
start_permanent: Mix.env() == :prod,
build_embedded: true,
aliases: [loadconfig: [&bootstrap/1]],
deps: deps(),
releases: [{@app, release()}],
preferred_cli_target: [run: :host, test: :host]
]
end
# Starting nerves_bootstrap adds the required aliases to Mix.Project.config()
# Aliases are only added if MIX_TARGET is set.
def bootstrap(args) do
Application.start(:nerves_bootstrap)
Mix.Task.run("loadconfig", args)
end
# Run "mix help compile.app" to learn about applications.
def application do
[
mod: {ExSpeedGame.Application, []},
extra_applications: [:logger, :runtime_tools]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# Dependencies for all targets
{:nerves, "~> 1.5.0", runtime: false},
{:shoehorn, "~> 0.6"},
{:ring_logger, "~> 0.6"},
{:toolshed, "~> 0.2"},
{:circuits_gpio, "~> 0.4"},
# Dependencies for all targets except :host
{:nerves_runtime, "~> 0.6", targets: @all_targets},
{:nerves_init_gadget, "~> 0.4", targets: @all_targets},
# Dependencies for specific targets
{:nerves_system_rpi0, "~> 1.10", runtime: false, targets: :rpi0}
]
end
def release do
[
overwrite: true,
cookie: "#{@app}_cookie",
include_erts: &Nerves.Release.erts/0,
steps: [&Nerves.Release.init/1, :assemble],
strip_beams: Mix.env() == :prod
]
end
end

36
mix.lock Normal file
View file

@ -0,0 +1,36 @@
%{
"circuits_gpio": {:hex, :circuits_gpio, "0.4.5", "4d5b0f707c425fc56f03086232259f65482a3d1f1cf15335253636d0bb846446", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "b42d28d60a6cfdfb6b21b66ab0b8c5de0ea5a32b390b61d2fe86a2ad8edb90ad"},
"dns": {:hex, :dns, "2.1.2", "81c46d39f7934f0e73368355126e4266762cf227ba61d5889635d83b2d64a493", [:mix], [{:socket, "~> 0.3.13", [hex: :socket, repo: "hexpm", optional: false]}], "hexpm", "6818589d8e59c03a2c73001e5cd7a957f99c30a796021aa32445ea14d0f3356b"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
"mdns": {:hex, :mdns, "1.0.3", "f08414daf5636bf5cd364611e838818e9250c91a3282a817ad9174b03e757401", [:mix], [{:dns, "~> 2.0", [hex: :dns, repo: "hexpm", optional: false]}], "hexpm", "6cb44eac9d1d71d0c5b400a383ccdc2b474d2e89a49a1e049e496637a6bab4c1"},
"muontrap": {:hex, :muontrap, "0.5.1", "98fe96d0e616ee518860803a37a29eb23ffc2ca900047cb1bb7fd37521010093", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3c11b7f151b202148912c73cbdd633b76fa68fabc26cc441c9d6d140e22290dc"},
"nerves": {:hex, :nerves, "1.5.4", "d5a2a29a642e92277d5680f40d0fadff17796e75faa82de87ba0bc920ffcf818", [:mix], [{:distillery, "~> 2.1", [hex: :distillery, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "283ce855f369ff209f3358d25e58f1ac941d58aef3ce7e8cc0be9919d25bf4f5"},
"nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.4.4", "12b0d9c84ec9f79c1b0ac0de1c575372ef972d0c58ce21c36bf354062c6222d9", [:mix], [{:nerves_runtime, "~> 0.6", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm", "98c40104d0d2c6e6e8cce22f8c8fd8ad5b4b97f8694e42a9101ca44befac38f0"},
"nerves_init_gadget": {:hex, :nerves_init_gadget, "0.7.0", "7402b190a7354fc4df53e707e1a8e421352ac6c3dba4262273d1f1a55bbb019b", [:mix], [{:mdns, "~> 1.0", [hex: :mdns, repo: "hexpm", optional: false]}, {:nerves_firmware_ssh, "~> 0.2", [hex: :nerves_firmware_ssh, repo: "hexpm", optional: false]}, {:nerves_network, "~> 0.3", [hex: :nerves_network, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.3", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:nerves_time, "~> 0.2", [hex: :nerves_time, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.1", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:ring_logger, "~> 0.4", [hex: :ring_logger, repo: "hexpm", optional: false]}], "hexpm", "989df1fd939545ae3f1a7185d7447273f7700b0a0bc204fbb1b18e03e6d38ad3"},
"nerves_network": {:hex, :nerves_network, "0.5.5", "4690c362707f76c4072810bd9639b2ae8eb7dd9c21119656308b462a087230aa", [:make, :mix], [{:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network_interface, "~> 0.4.4", [hex: :nerves_network_interface, repo: "hexpm", optional: false]}, {:nerves_wpa_supplicant, "~> 0.5", [hex: :nerves_wpa_supplicant, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.2.0", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.7", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm", "5e63529c6e128d147f5a6df82bd7daffd211057b8ac0c8ba625939a7fde9ccff"},
"nerves_network_interface": {:hex, :nerves_network_interface, "0.4.6", "d50e57daca8154f0f780fd98eb5ae94a005579e0d72d69840e80e228375d88ad", [:make, :mix], [{:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "94eb89db67ceb17a7f3465d55c05b00e8d8cf10aa812556745ce0c06868768d3"},
"nerves_runtime": {:hex, :nerves_runtime, "0.11.0", "96787c3935ec1a4943c8fcd63ce6ed1f2301f5f28eac41db800f3fafe0043f1f", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.8.0", [hex: :system_registry, repo: "hexpm", optional: false]}, {:uboot_env, "~> 0.1.1", [hex: :uboot_env, repo: "hexpm", optional: false]}], "hexpm", "7adc7f7163422bddd569daadcb5d9ceb3010687afcba9f3c147ad17361417d7e"},
"nerves_system_bbb": {:hex, :nerves_system_bbb, "2.5.2", "9fb8744f61bfcca57e8a2c9273ff7fe484e329b15275fadcbec3a910a6b8ac11", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.2.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm", "210fb7dd964cd6c5d2f9171d5198cf6d2ec52e0355351cabe6f329a7156064b3"},
"nerves_system_br": {:hex, :nerves_system_br, "1.10.2", "2cbf767e9da2c526c3a00720a3f7212b861735fed41f3abcbc2af4a960731d7c", [:mix], [], "hexpm", "e344a65a27c116aacbd8d165920af54bf436edcdcf7db91aa4a39f491fcffe31"},
"nerves_system_linter": {:hex, :nerves_system_linter, "0.3.0", "84e0f63c8ac196b16b77608bbe7df66dcf352845c4e4fb394bffd2b572025413", [:mix], [], "hexpm", "bffbdfb116bc72cde6e408c34c0670b199846e9a8f0953cc1c9f1eea693821a1"},
"nerves_system_rpi": {:hex, :nerves_system_rpi, "1.10.2", "b30bfa5470a2ac97aa200dbceee4261c625c5d18f241c684cb0477bf6acca07d", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.2.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm", "7548bf9a99be7ceef723ca016317aaeed8ff4dc8009f557ae53df8eac651d077"},
"nerves_system_rpi0": {:hex, :nerves_system_rpi0, "1.10.2", "5d4dbc3163d3a798bb7c2f52555e4b11cb1beab9e4ef325272aa15cd2cc5119a", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.2.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm", "834de3a59448ea756724a70ccf177a948fd1e12f3229ab3f294469b64e20f0ee"},
"nerves_system_rpi2": {:hex, :nerves_system_rpi2, "1.10.2", "d4ddc6e0fd85f48b2aba5a3c582d3f571bc84fabe9dbcc7e9d48a3624572dc98", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.2.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm", "53dd43843cdf4e98dc7453c981aab69fa84449a09de414a291dd23aecc7d8721"},
"nerves_system_rpi3": {:hex, :nerves_system_rpi3, "1.10.2", "a5efd36ce80940c427c386383cf0ba1d97b839822639573d3788e7cc25febf81", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.2.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm", "12f73065b61d87d0cda023424cf91e37fd64957f726380994c12a8d0fddca5a3"},
"nerves_system_rpi3a": {:hex, :nerves_system_rpi3a, "1.10.2", "f084f2c382500179e762e6e8b0750f34b8741d4f451cfbdb9eff99a694b916ee", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.2.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm", "26112289a7064c2a11f942e36ae02c69a54e2a9e97fe0194b885a46241073541"},
"nerves_system_rpi4": {:hex, :nerves_system_rpi4, "1.10.2", "af0f7d1985d849f571fd300f8e4f9e16dec6e82f0d7f1a67b293fd0f79c4d5d2", [:mix], [{:nerves, "~> 1.5", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.2.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm", "f146d4db7ec7623af2706b5d7fb2f27b5235019fe437d01223d02cfca0404d45"},
"nerves_system_x86_64": {:hex, :nerves_system_x86_64, "1.10.2", "20f0660c86616a9e0d373ef1e98068a7f05a1ccdd2b4282df2365f70a70ce194", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_x86_64_unknown_linux_musl, "1.2.0", [hex: :nerves_toolchain_x86_64_unknown_linux_musl, repo: "hexpm", optional: false]}], "hexpm", "57359eb25f0d0bfccfd73ff9a590b0c53c8bec328325330804a22af80453ed38"},
"nerves_time": {:hex, :nerves_time, "0.4.0", "8c2a6be23df71fe331407d1ab0c61334d8159c4acd55f777a0c835a6b6f81483", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:muontrap, "~> 0.5", [hex: :muontrap, repo: "hexpm", optional: false]}], "hexpm", "51dd7a34fa346647214f94b201cf524b182c62c4f9f44656bc1910823028dd59"},
"nerves_toolchain_arm_unknown_linux_gnueabihf": {:hex, :nerves_toolchain_arm_unknown_linux_gnueabihf, "1.2.0", "ba48ce7c846ee12dfca8148dc7240988d96a3f2eb9c234bf08bffe4f0f7a3c62", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.6.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm", "18df425fee48a9088bf941d3615c677b818b537310123c4b4c90b710e4a34180"},
"nerves_toolchain_armv6_rpi_linux_gnueabi": {:hex, :nerves_toolchain_armv6_rpi_linux_gnueabi, "1.2.0", "007668c7ad1f73bad8fd54ad1a27a3b0fb91bca51b4af6bb3bbdac968ccae0ba", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.6.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm", "4e843f405d4b8e6137419f94e5b8f491bff5a87b02ac2223e126182e8cec4256"},
"nerves_toolchain_ctng": {:hex, :nerves_toolchain_ctng, "1.6.0", "452f8589c1a58ac787477caab20a8cfc6671e345837ccc19beefe49ae35ba983", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}], "hexpm", "7ee5744dc606c6debf3e459ef122e77c13d6a1be9e093f7e29af3759896f9dbb"},
"nerves_toolchain_x86_64_unknown_linux_musl": {:hex, :nerves_toolchain_x86_64_unknown_linux_musl, "1.2.0", "fbe688fa561b03190765e269d4336333c4961a48d2acd3f6cb283443a058e138", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.6.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm", "9cb676b7fb7a4564b18e1e16f72bc64f06f77346a477033fde6e4c2d1cc2ecff"},
"nerves_wpa_supplicant": {:hex, :nerves_wpa_supplicant, "0.5.2", "4ec392fc08faf35f50d1070446c2e5019f6b85bd53f5716f904e3f75716d9596", [:make, :mix], [{:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "c286ed5397fa185ab986226596eeb72d8f5f9f1b1156103418193f85905da713"},
"one_dhcpd": {:hex, :one_dhcpd, "0.2.4", "2664f2e1ac72cbae0474a88d1a3d55019ccc3ee582ded382589511627a8ed516", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "38f08c228d066153dbe352b150910345e395eacd2db3c19085d54c0baeeebacb"},
"ring_logger": {:hex, :ring_logger, "0.8.0", "b1baddc269099b2afe2ea3a87b8e2b71e57331c0000038ae55090068aac679db", [:mix], [], "hexpm", "9b2f482e4346c13c11ef555f898202d0ddbfda6e2354e5c6e0559d2b4e0cf781"},
"shoehorn": {:hex, :shoehorn, "0.6.0", "f9a1b7d6212cf18ba91c4f71c26076059df33cea4db2eb3c098bfa6673349412", [:mix], [{:distillery, "~> 2.1", [hex: :distillery, repo: "hexpm", optional: true]}], "hexpm", "e54a1f58a121caf8f0f3a355686b2661258b1bc0d4fffef8923bd7b11c2f9d79"},
"socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "hexpm", "f82ea9833ef49dde272e6568ab8aac657a636acb4cf44a7de8a935acb8957c2e"},
"system_registry": {:hex, :system_registry, "0.8.2", "df791dc276652fcfb53be4dab823e05f8269b96ac57c26f86a67838dbc0eefe7", [:mix], [], "hexpm", "f7acdede22c73ab0b3735eead7f2095efb2a7a6198366564205274db2ca2a8f8"},
"toolshed": {:hex, :toolshed, "0.2.11", "0cd5312bd6a48f5b654b6ffa9239a63af9f3d200da414790fe25f066e14842a9", [:mix], [{:nerves_runtime, "~> 0.8", [hex: :nerves_runtime, repo: "hexpm", optional: true]}], "hexpm", "f22ae95d77136f9f7db93cddd40d42bc8252d825f15a772a17d4c7947b6faad5"},
"uboot_env": {:hex, :uboot_env, "0.1.1", "b01e3ec0973e99473234f27839e29e63b5b81eba6a136a18a78d049d4813d6c5", [:mix], [], "hexpm", "f7b82da0cb40c8db9c9fb1fc977780ab0c28d961ec1f3c7ab265c4352e4141ae"},
}

43
rel/vm.args.eex Normal file
View file

@ -0,0 +1,43 @@
## Add custom options here
## Distributed Erlang Options
## The cookie needs to be configured prior to vm boot for
## for read only filesystem.
-setcookie <%= @release.options[:cookie] %>
## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's
## break handler and possibly exiting the VM.
+Bc
# Allow time warps so that the Erlang system time can more closely match the
# OS system time.
+C multi_time_warp
## Load code at system startup
## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy
-mode embedded
## Save the shell history between reboots
## See http://erlang.org/doc/man/kernel_app.html for additional options
-kernel shell_history enabled
## Enable heartbeat monitoring of the Erlang runtime system
-heart -env HEART_BEAT_TIMEOUT 30
## Start the Elixir shell
-noshell
-user Elixir.IEx.CLI
## Enable colors in the shell
-elixir ansi_enabled true
## Options added after -extra are interpreted as plain arguments and can be
## retrieved using :init.get_plain_arguments(). Options before the "--" are
## interpreted by Elixir and anything afterwards is left around for other IEx
## and user applications.
-extra --no-halt
--
--dot-iex /etc/iex.exs

View file

@ -0,0 +1,18 @@
# Add Toolshed helpers to the IEx session
use Toolshed
if RingLogger in Application.get_env(:logger, :backends, []) do
IO.puts """
RingLogger is collecting log messages from Elixir and Linux. To see the
messages, either attach the current IEx session to the logger:
RingLogger.attach
or print the next messages in the log:
RingLogger.next
"""
end
# Be careful when adding to this file. Nearly any error can crash the VM and
# cause a reboot.

View file

@ -0,0 +1,8 @@
defmodule ExSpeedGameTest do
use ExUnit.Case
doctest ExSpeedGame
test "greets the world" do
assert ExSpeedGame.hello() == :world
end
end

1
test/test_helper.exs Normal file
View file

@ -0,0 +1 @@
ExUnit.start()

139
upload.sh Executable file
View file

@ -0,0 +1,139 @@
#!/bin/sh
#
# Upload new firmware to a target running nerves_firmware_ssh
#
# Usage:
# upload.sh [destination IP] [Path to .fw file]
#
# If unspecifed, the destination is nerves.local and the .fw file is naively
# guessed
#
# You may want to add the following to your `~/.ssh/config` to avoid recording
# the IP addresses of the target:
#
# Host nerves.local
# UserKnownHostsFile /dev/null
# StrictHostKeyChecking no
#
# The firmware update protocol is:
#
# 1. Connect to the nerves_firmware_ssh service running on port 8989
# 2. Send "fwup:$FILESIZE,reboot\n" where `$FILESIZE` is the size of the file
# being uploaded
# 3. Send the firmware file
# 4. The response from the device is a progress bar from fwup that can either
# be ignored or shown to the user.
# 5. The ssh connection is closed with an exit code to indicate success or
# failure
#
# Feel free to copy this script whereever is convenient. The template is at
# https://github.com/nerves-project/nerves_firmware_ssh/blob/master/priv/templates/script.upload.eex
#
set -e
DESTINATION=$1
FILENAME="$2"
help() {
echo
echo "upload.sh [destination IP] [Path to .fw file]"
echo
echo "Default destination IP is 'nerves.local'"
echo "Default firmware bundle is the first .fw file in '_build/\${MIX_TARGET}_\${MIX_ENV}/nerves/images'"
echo
echo "MIX_TARGET=$MIX_TARGET"
echo "MIX_ENV=$MIX_ENV"
exit 1
}
[ -n "$DESTINATION" ] || DESTINATION=nerves.local
[ -n "$MIX_TARGET" ] || MIX_TARGET=rpi0
[ -n "$MIX_ENV" ] || MIX_ENV=dev
if [ -z "$FILENAME" ]; then
FIRMWARE_PATH="./_build/${MIX_TARGET}_${MIX_ENV}/nerves/images"
if [ ! -d "$FIRMWARE_PATH" ]; then
# Try the Nerves 1.4 path if the user hasn't upgraded their mix.exs
FIRMWARE_PATH="./_build/${MIX_TARGET}/${MIX_TARGET}_${MIX_ENV}/nerves/images"
if [ ! -d "$FIRMWARE_PATH" ]; then
# Try the pre-Nerves 1.4 path
FIRMWARE_PATH="./_build/${MIX_TARGET}/${MIX_ENV}/nerves/images"
if [ ! -d "$FIRMWARE_PATH" ]; then
echo "Can't find the build products. Specify path to .fw file or try running 'mix firmware'"
exit 1
fi
fi
fi
FILENAME=$(ls "$FIRMWARE_PATH/"*.fw 2> /dev/null | head -n 1)
fi
[ -n "$FILENAME" ] || (echo "Error: error determining firmware bundle."; help)
[ -f "$FILENAME" ] || (echo "Error: can't find '$FILENAME'"; help)
# Check the flavor of stat for sending the filesize
if stat --version 2>/dev/null | grep GNU >/dev/null; then
# The QNU way
FILESIZE=$(stat -c%s "$FILENAME")
else
# Else default to the BSD way
FILESIZE=$(stat -f %z "$FILENAME")
fi
FIRMWARE_METADATA=$(fwup -m -i "$FILENAME" || echo "meta-product=Error reading metadata!")
FIRMWARE_PRODUCT=$(echo "$FIRMWARE_METADATA" | grep -E "^meta-product=" -m 1 2>/dev/null | cut -d '=' -f 2- | tr -d '"')
FIRMWARE_VERSION=$(echo "$FIRMWARE_METADATA" | grep -E "^meta-version=" -m 1 2>/dev/null | cut -d '=' -f 2- | tr -d '"')
FIRMWARE_PLATFORM=$(echo "$FIRMWARE_METADATA" | grep -E "^meta-platform=" -m 1 2>/dev/null | cut -d '=' -f 2- | tr -d '"')
FIRMWARE_UUID=$(echo "$FIRMWARE_METADATA" | grep -E "^meta-uuid=" -m 1 2>/dev/null | cut -d '=' -f 2- | tr -d '"')
echo "Path: $FILENAME"
echo "Product: $FIRMWARE_PRODUCT $FIRMWARE_VERSION"
echo "UUID: $FIRMWARE_UUID"
echo "Platform: $FIRMWARE_PLATFORM"
echo
echo "Uploading to $DESTINATION..."
# Don't fall back to asking for passwords, since that won't work
# and it's easy to misread the message thinking that it's asking
# for the private key password
SSH_OPTIONS="-o PreferredAuthentications=publickey"
if [ "$(uname -s)" = "Darwin" ]; then
DESTINATION_IP=$(arp -n $DESTINATION | sed 's/.* (\([0-9.]*\).*/\1/' || exit 0)
if [ -z "$DESTINATION_IP" ]; then
echo "Can't resolve $DESTINATION"
exit 1
fi
TEST_DESTINATION_IP=$(printf "$DESTINATION_IP" | head -n 1)
if [ "$DESTINATION_IP" != "$TEST_DESTINATION_IP" ]; then
echo "Multiple destination IP addresses for $DESTINATION found:"
echo "$DESTINATION_IP"
echo "Guessing the first one..."
DESTINATION_IP=$TEST_DESTINATION_IP
fi
IS_DEST_LL=$(echo $DESTINATION_IP | grep '^169\.254\.' || exit 0)
if [ -n "$IS_DEST_LL" ]; then
LINK_LOCAL_IP=$(ifconfig | grep 169.254 | sed 's/.*inet \([0-9.]*\) .*/\1/')
if [ -z "$LINK_LOCAL_IP" ]; then
echo "Can't find an interface with a link local address?"
exit 1
fi
TEST_LINK_LOCAL_IP=$(printf "$LINK_LOCAL_IP" | tail -n 1)
if [ "$LINK_LOCAL_IP" != "$TEST_LINK_LOCAL_IP" ]; then
echo "Multiple interfaces with link local addresses:"
echo "$LINK_LOCAL_IP"
echo "Guessing the last one, but YMMV..."
LINK_LOCAL_IP=$TEST_LINK_LOCAL_IP
fi
# If a link local address, then force ssh to bind to the link local IP
# when connecting. This fixes an issue where the ssh connection is bound
# to another Ethernet interface. The TCP SYN packet that goes out has no
# chance of working when this happens.
SSH_OPTIONS="$SSH_OPTIONS -b $LINK_LOCAL_IP"
fi
fi
printf "fwup:$FILESIZE,reboot\n" | cat - $FILENAME | ssh -s -p 8989 $SSH_OPTIONS $DESTINATION nerves_firmware_ssh