├── .circleci └── config.yml ├── .github └── dependabot.yml ├── .tool-versions ├── LICENSES └── CC0-1.0.txt ├── README.md ├── REUSE.toml ├── blinky ├── .formatter.exs ├── .gitignore ├── README.md ├── assets │ └── rpi0-onboard-led.jpg ├── config │ ├── bbb.exs │ ├── config.exs │ ├── ev3.exs │ ├── grisp2.exs │ ├── host.exs │ ├── mangopi_mq_pro.exs │ ├── osd32mp1.exs │ ├── rpi.exs │ ├── rpi0.exs │ ├── rpi2.exs │ ├── rpi3.exs │ ├── rpi3a.exs │ ├── rpi4.exs │ ├── rpi5.exs │ ├── target.exs │ └── x86_64.exs ├── lib │ ├── blinky.ex │ └── blinky │ │ └── application.ex ├── mix.exs ├── mix.lock ├── rel │ └── vm.args.eex └── rootfs_overlay │ └── etc │ └── iex.exs ├── hello_erlang ├── .gitignore ├── README.md ├── config │ ├── config.exs │ ├── host.exs │ └── target.exs ├── mix.exs ├── mix.lock ├── rel │ └── vm.args.eex ├── rootfs_overlay │ └── .keep └── src │ ├── hello_erlang_app.erl │ ├── hello_erlang_supervisor.erl │ └── hello_erlang_worker.erl ├── hello_gpio ├── .formatter.exs ├── .gitignore ├── README.md ├── assets │ ├── GPIO-input.png │ └── gpio.png ├── config │ ├── config.exs │ ├── host.exs │ └── target.exs ├── lib │ └── hello_gpio.ex ├── mix.exs ├── mix.lock ├── rel │ └── vm.args.eex └── rootfs_overlay │ └── etc │ └── iex.exs ├── hello_lfe ├── .formatter.exs ├── .gitignore ├── README.md ├── assets │ └── shell.png ├── config │ ├── config.exs │ ├── host.exs │ └── target.exs ├── lib │ └── mix │ │ ├── compilers │ │ └── lfe.ex │ │ └── tasks │ │ └── compile.lfe.ex ├── mix.exs ├── mix.lock ├── rel │ └── vm.args.eex ├── rootfs_overlay │ ├── .keep │ └── etc │ │ └── .placeholder └── src │ ├── hello-sup.lfe │ ├── hello-worker.lfe │ └── hello_lfe.lfe ├── hello_live_view ├── .formatter.exs ├── .gitignore ├── README.md ├── assets │ ├── css │ │ └── app.css │ ├── js │ │ └── app.js │ ├── tailwind.config.js │ └── vendor │ │ └── topbar.js ├── config │ ├── config.exs │ ├── dev.exs │ ├── host.exs │ ├── prod.exs │ ├── runtime.exs │ ├── target.exs │ └── test.exs ├── lib │ ├── hello_live_view.ex │ ├── hello_live_view │ │ └── application.ex │ ├── hello_live_view_web.ex │ └── hello_live_view_web │ │ ├── components │ │ ├── core_components.ex │ │ ├── layouts.ex │ │ ├── layouts │ │ │ ├── app.html.heex │ │ │ └── root.html.heex │ │ └── text.ex │ │ ├── controllers │ │ ├── error_html.ex │ │ └── error_json.ex │ │ ├── endpoint.ex │ │ ├── gettext.ex │ │ ├── live │ │ └── home.ex │ │ ├── router.ex │ │ ├── telemetry.ex │ │ └── templates │ │ └── layout │ │ ├── app.html.heex │ │ └── root.html.heex ├── mix.exs ├── mix.lock ├── priv │ ├── gettext │ │ ├── en │ │ │ └── LC_MESSAGES │ │ │ │ └── errors.po │ │ └── errors.pot │ └── static │ │ ├── favicon.ico │ │ ├── images │ │ └── nerves.svg │ │ └── robots.txt ├── rel │ └── vm.args.eex ├── rootfs_overlay │ └── etc │ │ └── iex.exs ├── scripts │ └── deploy.sh └── test │ ├── hello_live_view_web │ └── controllers │ │ ├── error_html_test.exs │ │ └── error_json_test.exs │ ├── support │ └── conn_case.ex │ └── test_helper.exs ├── hello_scenic ├── .formatter.exs ├── .gitignore ├── .skip-bbb ├── .skip-grisp2 ├── .skip-osd32mp1 ├── .skip-x86_64 ├── README.md ├── assets │ └── readme.txt ├── config │ ├── config.exs │ ├── host.exs │ └── target.exs ├── helloworld.png ├── lib │ ├── assets.ex │ ├── components │ │ └── readme.txt │ ├── hello_scenic.ex │ ├── pubsub │ │ ├── readme.txt │ │ └── supervisor.ex │ └── scenes │ │ ├── home.ex │ │ └── readme.txt ├── mix.exs ├── mix.lock ├── rel │ └── vm.args.eex └── rootfs_overlay │ └── etc │ └── iex.exs ├── hello_snmp_agent ├── .formatter.exs ├── .gitignore ├── README.md ├── config │ ├── config.exs │ ├── host.exs │ └── target.exs ├── lib │ ├── hello_snmp_agent.ex │ └── hello_snmp_agent │ │ ├── application.ex │ │ └── snmp │ │ ├── agent.ex │ │ └── mib │ │ ├── framework.ex │ │ ├── my_mib.ex │ │ └── standard.ex ├── mibs │ └── MY-MIB.mib ├── mix.exs ├── mix.lock ├── rel │ └── vm.args.eex └── rootfs_overlay │ └── etc │ └── iex.exs ├── hello_snmp_manager ├── .formatter.exs ├── .gitignore ├── README.md ├── config │ ├── config.exs │ ├── host.exs │ └── target.exs ├── lib │ ├── hello_snmp_manager.ex │ └── hello_snmp_manager │ │ └── application.ex ├── mix.exs ├── mix.lock ├── rel │ └── vm.args.eex └── rootfs_overlay │ ├── etc │ └── iex.exs │ └── snmp │ ├── agents.conf │ ├── manager.conf │ └── users.conf ├── hello_sqlite ├── .formatter.exs ├── .gitignore ├── README.md ├── config │ ├── config.exs │ ├── host.exs │ └── target.exs ├── lib │ ├── hello_sqlite.ex │ └── hello_sqlite │ │ ├── application.ex │ │ ├── migration_helpers.ex │ │ ├── repo.ex │ │ ├── scheduler_usage.ex │ │ └── scheduler_usage_poller.ex ├── mix.exs ├── mix.lock ├── priv │ └── repo │ │ └── migrations │ │ ├── .formatter.exs │ │ └── 20210318212322_add_scheduler_usage.exs ├── rel │ └── vm.args.eex └── rootfs_overlay │ └── etc │ └── iex.exs ├── hello_wifi ├── .formatter.exs ├── .gitignore ├── README.md ├── assets │ └── pinout-xyz.png ├── config │ ├── config.exs │ ├── host.exs │ └── target.exs ├── lib │ ├── button.ex │ └── hello_wifi.ex ├── mix.exs ├── mix.lock ├── rel │ └── vm.args.eex └── rootfs_overlay │ └── etc │ └── iex.exs ├── hello_zig ├── .formatter.exs ├── .gitignore ├── .requires-zig ├── .skip-mangopi_mq_pro ├── README.md ├── config │ ├── config.exs │ ├── host.exs │ └── target.exs ├── lib │ ├── hello_zig.ex │ ├── hello_zig │ │ └── application.ex │ └── strings.zig ├── mix.exs ├── rel │ └── vm.args.eex ├── rootfs_overlay │ └── etc │ │ └── iex.exs └── test │ ├── hello_zig_test.exs │ └── test_helper.exs ├── minimal ├── .formatter.exs ├── .gitignore ├── README.md ├── config │ ├── config.exs │ ├── host.exs │ └── target.exs ├── lib │ ├── minimal.ex │ └── minimal │ │ └── application.ex ├── mix.exs ├── mix.lock └── rel │ └── vm.args.eex ├── poncho_phoenix ├── README.md ├── firmware │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── config │ │ ├── config.exs │ │ ├── host.exs │ │ └── target.exs │ ├── lib │ │ ├── firmware.ex │ │ └── firmware │ │ │ ├── application.ex │ │ │ └── migration_helpers.ex │ ├── mix.exs │ ├── mix.lock │ ├── rel │ │ └── vm.args.eex │ ├── rootfs_overlay │ │ └── etc │ │ │ └── iex.exs │ └── test │ │ ├── firmware_test.exs │ │ └── test_helper.exs └── ui │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── assets │ ├── css │ │ └── app.css │ ├── js │ │ └── app.js │ ├── tailwind.config.js │ └── vendor │ │ └── topbar.js │ ├── config │ ├── config.exs │ ├── dev.exs │ ├── prod.exs │ ├── runtime.exs │ └── test.exs │ ├── lib │ ├── ui.ex │ ├── ui │ │ ├── accounts.ex │ │ ├── accounts │ │ │ └── user.ex │ │ ├── application.ex │ │ └── repo.ex │ ├── ui_web.ex │ └── ui_web │ │ ├── components │ │ ├── core_components.ex │ │ ├── layouts.ex │ │ └── layouts │ │ │ ├── app.html.heex │ │ │ └── root.html.heex │ │ ├── controllers │ │ ├── error_html.ex │ │ ├── error_json.ex │ │ ├── page_controller.ex │ │ ├── page_html.ex │ │ └── page_html │ │ │ └── home.html.heex │ │ ├── endpoint.ex │ │ ├── gettext.ex │ │ ├── live │ │ ├── page_live.ex │ │ ├── page_live.html.heex │ │ └── user_live │ │ │ ├── form_component.ex │ │ │ ├── index.ex │ │ │ ├── index.html.heex │ │ │ ├── show.ex │ │ │ └── show.html.heex │ │ ├── router.ex │ │ └── telemetry.ex │ ├── mix.exs │ ├── mix.lock │ ├── priv │ ├── gettext │ │ ├── en │ │ │ └── LC_MESSAGES │ │ │ │ └── errors.po │ │ └── errors.pot │ ├── repo │ │ ├── migrations │ │ │ ├── .formatter.exs │ │ │ └── 20211206175437_create_users.exs │ │ └── seeds.exs │ └── static │ │ ├── favicon.ico │ │ ├── images │ │ └── logo.svg │ │ └── robots.txt │ └── test │ ├── support │ ├── conn_case.ex │ └── data_case.ex │ ├── test_helper.exs │ └── ui_web │ └── controllers │ ├── error_html_test.exs │ ├── error_json_test.exs │ └── page_controller_test.exs └── scripts ├── build-all.sh ├── clean-all.sh ├── format-all.sh ├── projects.sh └── update-deps-all.sh /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 27.3.3 2 | elixir 1.18.3-otp-27 3 | fwup 1.12.0 4 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[annotations]] 4 | path = [ 5 | ".circleci/config.yml", 6 | ".github/dependabot.yml", 7 | ".tool-versions", 8 | "blinky/**", 9 | "minimal/**", 10 | "hello_erlang/**", 11 | "hello_gpio/**", 12 | "hello_lfe/**", 13 | "hello_live_view/**", 14 | "poncho_phoenix/**", 15 | "hello_scenic/**", 16 | "hello_snmp_agent/**", 17 | "hello_snmp_manager/**", 18 | "hello_sqlite/**", 19 | "hello_wifi/**", 20 | "hello_zig/**", 21 | "README.md", 22 | "REUSE.toml", 23 | "scripts/**", 24 | ] 25 | precedence = "aggregate" 26 | SPDX-FileCopyrightText = "None" 27 | SPDX-License-Identifier = "CC0-1.0" 28 | -------------------------------------------------------------------------------- /blinky/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: [ 4 | "{mix,.formatter}.exs", 5 | "{config,lib,test}/**/*.{ex,exs}", 6 | "rootfs_overlay/etc/iex.exs" 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /blinky/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | -------------------------------------------------------------------------------- /blinky/assets/rpi0-onboard-led.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/blinky/assets/rpi0-onboard-led.jpg -------------------------------------------------------------------------------- /blinky/config/bbb.exs: -------------------------------------------------------------------------------- 1 | # configuration for Beaglebone Black (target bbb) 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{ 7 | green: "beaglebone:green:usr0" 8 | }, 9 | led1: %{ 10 | green: "beaglebone:green:usr1" 11 | }, 12 | led2: %{ 13 | green: "beaglebone:green:usr2" 14 | }, 15 | led3: %{ 16 | green: "beaglebone:green:usr3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /blinky/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and is restricted to 5 | # this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | config :blinky, target: Mix.target() 12 | 13 | # Customize non-Elixir parts of the firmware. See 14 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 15 | 16 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 17 | 18 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 19 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 20 | 21 | config :nerves, source_date_epoch: "1577974981" 22 | 23 | # Use Ringlogger as the logger backend and remove :console. 24 | # See https://hexdocs.pm/ring_logger/readme.html for more information on 25 | # configuring ring_logger. 26 | 27 | config :logger, backends: [RingLogger] 28 | 29 | if Mix.target() == :host do 30 | import_config "host.exs" 31 | else 32 | import_config "target.exs" 33 | end 34 | -------------------------------------------------------------------------------- /blinky/config/ev3.exs: -------------------------------------------------------------------------------- 1 | # Configuration for the Lego Mindstorms EV3 brick (target ev3) 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{ 7 | red: "ev3:left:red:ev3dev", 8 | green: "ev3:left:green:ev3dev" 9 | }, 10 | right_led: %{ 11 | red: "ev3:right:red:ev3dev", 12 | green: "ev3:right:green:ev3dev" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /blinky/config/grisp2.exs: -------------------------------------------------------------------------------- 1 | # Configuration for the GRiSPv2 (target grisp2) 2 | import Config 3 | 4 | # The GRiSP 2 has two RGB LEDs and a green LED 5 | # 6 | # RGB1: grisp-rgb1-red, grisp-rgb1-green, grisp-rgb1-blue 7 | # RGB2: grisp-rgb2-red, grisp-rgb2-green, grisp-rgb2-blue 8 | # phycore-green - defaults to heartbeat on boot 9 | 10 | config :blinky, 11 | indicators: %{ 12 | default: %{red: "grisp-rgb1-red", green: "grisp-rgb1-green", blue: "grisp-rgb1-blue"}, 13 | rgb2: %{red: "grisp-rgb2-red", green: "grisp-rgb2-green", blue: "grisp-rgb2-blue"}, 14 | phycore: %{green: "phycore-green"} 15 | } 16 | -------------------------------------------------------------------------------- /blinky/config/host.exs: -------------------------------------------------------------------------------- 1 | # Configuration for the host (no LEDs to blink) 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{} 7 | } 8 | -------------------------------------------------------------------------------- /blinky/config/mangopi_mq_pro.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | config :blinky, indicators: %{default: %{blue: "blue:status"}} 4 | -------------------------------------------------------------------------------- /blinky/config/osd32mp1.exs: -------------------------------------------------------------------------------- 1 | # Allow project to build, but leave configuration up to the user 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{} 7 | } 8 | -------------------------------------------------------------------------------- /blinky/config/rpi.exs: -------------------------------------------------------------------------------- 1 | # Configuration for the Raspberry Pi A+ / B+ / B / Zero (target rpi) 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{ 7 | green: "ACT" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /blinky/config/rpi0.exs: -------------------------------------------------------------------------------- 1 | # Configuration for the Raspberry Pi Zero (target rpi0) 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{ 7 | green: "ACT" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /blinky/config/rpi2.exs: -------------------------------------------------------------------------------- 1 | # Configuration for the Raspberry Pi 2 Model B (target rpi2) 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{ 7 | green: "ACT" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /blinky/config/rpi3.exs: -------------------------------------------------------------------------------- 1 | # Configuration for the Raspberry Pi 3 (target rpi3) 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{ 7 | green: "ACT" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /blinky/config/rpi3a.exs: -------------------------------------------------------------------------------- 1 | # Configuration for the Raspberry Pi 3 Model A (target rpi3a) 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{ 7 | green: "ACT" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /blinky/config/rpi4.exs: -------------------------------------------------------------------------------- 1 | # Configuration for the Raspberry Pi 4 (target rpi4) 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{ 7 | green: "ACT" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /blinky/config/rpi5.exs: -------------------------------------------------------------------------------- 1 | # Configuration for the Raspberry Pi 5 (target rpi5) 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{ 7 | green: "ACT" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /blinky/config/x86_64.exs: -------------------------------------------------------------------------------- 1 | # Allow project to build, but leave configuration up to the user 2 | import Config 3 | 4 | config :blinky, 5 | indicators: %{ 6 | default: %{} 7 | } 8 | -------------------------------------------------------------------------------- /blinky/lib/blinky.ex: -------------------------------------------------------------------------------- 1 | defmodule Blinky do 2 | @moduledoc """ 3 | Simple example to blink a list of LEDs forever. 4 | 5 | The list of LEDs is platform-dependent, and defined in the config directory 6 | (see config.exs). See README.md for build instructions. 7 | """ 8 | end 9 | -------------------------------------------------------------------------------- /blinky/lib/blinky/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Blinky.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | require Logger 8 | 9 | @impl true 10 | def start(_type, _args) do 11 | delux_options = Application.get_all_env(:blinky) 12 | Logger.debug("Blinky: target-specific options for Delux: #{inspect(delux_options)}") 13 | 14 | children = [ 15 | # See https://hexdocs.pm/delux 16 | {Delux, delux_options ++ [initial: Delux.Effects.blink(:on, 2)]} 17 | ] 18 | 19 | # See https://hexdocs.pm/elixir/Supervisor.html 20 | # for other strategies and supported options 21 | opts = [strategy: :one_for_one, name: Blinky.Supervisor] 22 | Supervisor.start_link(children, opts) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /blinky/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Add custom options here 2 | 3 | ## Distributed Erlang Options 4 | ## The cookie needs to be configured prior to vm boot for 5 | ## for read only filesystem. 6 | 7 | -setcookie <%= @release.options[:cookie] %> 8 | 9 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 10 | ## break handler and possibly exiting the VM. 11 | +Bc 12 | 13 | # Allow time warps so that the Erlang system time can more closely match the 14 | # OS system time. 15 | +C multi_time_warp 16 | 17 | ## Load code at system startup 18 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 19 | -mode embedded 20 | 21 | # Load code as per the boot script since not using archives 22 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 23 | -code_path_choice strict 24 | 25 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 26 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 27 | +sbwt none 28 | +sbwtdcpu none 29 | +sbwtdio none 30 | 31 | ## Save the shell history between reboots 32 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 33 | -kernel shell_history enabled 34 | 35 | ## Enable heartbeat monitoring of the Erlang runtime system 36 | -heart -env HEART_BEAT_TIMEOUT 30 37 | 38 | ## Start the Elixir shell 39 | 40 | -noshell 41 | -user elixir 42 | -run elixir start_cli 43 | 44 | ## Enable colors in the shell 45 | -elixir ansi_enabled true 46 | 47 | ## Options added after -extra are interpreted as plain arguments and can be 48 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 49 | ## interpreted by Elixir and anything afterwards is left around for other IEx 50 | ## and user applications. 51 | -extra --no-halt 52 | -- 53 | --dot-iex /etc/iex.exs 54 | -------------------------------------------------------------------------------- /blinky/rootfs_overlay/etc/iex.exs: -------------------------------------------------------------------------------- 1 | NervesMOTD.print() 2 | 3 | # Add Toolshed helpers to the IEx session 4 | use Toolshed 5 | -------------------------------------------------------------------------------- /hello_erlang/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | -------------------------------------------------------------------------------- /hello_erlang/README.md: -------------------------------------------------------------------------------- 1 | # HelloErlang 2 | 3 | This demonstrates a simple Nerves project that's written in Erlang. 4 | 5 | ## Erlang-izing a Nerves project 6 | 7 | Making Nerves support Erlang or more about deleting the Elixir parts that are 8 | added with the new project generator. Currently we only support using Elixir's 9 | `mix` build tool in the top level project, but you should be able to use other 10 | tools like `rebar3` in dependencies. 11 | 12 | The following describes what needed to be done after creating a Nerves project 13 | the "normal" Elixir-based way using `mix nerves.new `. 14 | 15 | The first step is to delete the `lib` and `test` directories and all of the code 16 | inside of them. Didn't that feel good? 17 | 18 | The next step is to update the `rel/vm.args` and delete everything that starts 19 | the Elixir shell. Look for `-noshell` and delete from there to the end. 20 | 21 | Finally, create a `src` directory and put your Erlang code in there like you 22 | would a normal Erlang project. 23 | 24 | ## Building 25 | 26 | Building follows the standard Nerves recipe. Here's an example: 27 | 28 | ```sh 29 | export MIX_TARGET=bbb 30 | mix deps.get 31 | mix firmware 32 | ``` 33 | 34 | And then burn an SD card using `mix firmware.burn`. 35 | 36 | ## Using 37 | 38 | The images pull in the standard `nerves_pack` infrastructure for bringing 39 | up networking and other initialization steps. See the `config/config.exs` for 40 | network parameters and other configuration. 41 | 42 | ## Running 43 | 44 | Attach to the console of your board (i.e., RPi3 is HDMI, BBB/RPi0 is USB 45 | gadget). You should see prints from the application. Note that the first boot 46 | takes quite a bit longer than the rest since it initializes the application's 47 | writable filesystem. 48 | 49 | To see log messages, run: 50 | 51 | ```erlang 52 | > 'Elixir.RingLogger':next(). 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /hello_erlang/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | config :hello_erlang, target: Mix.target() 12 | 13 | # Customize non-Elixir parts of the firmware. See 14 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 15 | 16 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 17 | 18 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 19 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 20 | 21 | config :nerves, source_date_epoch: "1577975236" 22 | 23 | # Use Ringlogger as the logger backend and remove :console. 24 | # See https://hexdocs.pm/ring_logger/readme.html for more information on 25 | # configuring ring_logger. 26 | 27 | config :logger, backends: [RingLogger] 28 | 29 | if Mix.target() == :host do 30 | import_config "host.exs" 31 | else 32 | import_config "target.exs" 33 | end 34 | -------------------------------------------------------------------------------- /hello_erlang/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | -------------------------------------------------------------------------------- /hello_erlang/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule HelloErlang.MixProject do 2 | use Mix.Project 3 | 4 | @app :hello_erlang 5 | @version "0.1.0" 6 | @all_targets [ 7 | :rpi, 8 | :rpi0, 9 | :rpi2, 10 | :rpi3, 11 | :rpi3a, 12 | :rpi4, 13 | :rpi5, 14 | :bbb, 15 | :osd32mp1, 16 | :x86_64, 17 | :grisp2, 18 | :mangopi_mq_pro 19 | ] 20 | 21 | def project do 22 | [ 23 | app: @app, 24 | version: @version, 25 | elixir: "~> 1.16", 26 | archives: [nerves_bootstrap: "~> 1.13"], 27 | start_permanent: Mix.env() == :prod, 28 | deps: deps(), 29 | releases: [{@app, release()}], 30 | preferred_cli_target: [run: :host, test: :host] 31 | ] 32 | end 33 | 34 | # Run "mix help compile.app" to learn about applications. 35 | def application do 36 | [ 37 | mod: {:hello_erlang_app, []}, 38 | extra_applications: [:logger, :runtime_tools] 39 | ] 40 | end 41 | 42 | # Run "mix help deps" to learn about dependencies. 43 | defp deps do 44 | [ 45 | # Dependencies for all targets 46 | {:nerves, "~> 1.10", runtime: false}, 47 | {:shoehorn, "~> 0.9.0"}, 48 | {:ring_logger, "~> 0.11.0"}, 49 | 50 | # Dependencies for all targets except :host 51 | {:nerves_runtime, "~> 0.13.0", targets: @all_targets}, 52 | {:nerves_pack, "~> 0.7.0", targets: @all_targets}, 53 | 54 | # Dependencies for specific targets 55 | {:nerves_system_rpi, "~> 1.13", runtime: false, targets: :rpi}, 56 | {:nerves_system_rpi0, "~> 1.13", runtime: false, targets: :rpi0}, 57 | {:nerves_system_rpi2, "~> 1.13", runtime: false, targets: :rpi2}, 58 | {:nerves_system_rpi3, "~> 1.13", runtime: false, targets: :rpi3}, 59 | {:nerves_system_rpi3a, "~> 1.13", runtime: false, targets: :rpi3a}, 60 | {:nerves_system_rpi4, "~> 1.13", runtime: false, targets: :rpi4}, 61 | {:nerves_system_rpi5, "~> 0.3", runtime: false, targets: :rpi5}, 62 | {:nerves_system_bbb, "~> 2.8", runtime: false, targets: :bbb}, 63 | {:nerves_system_osd32mp1, "~> 0.4", runtime: false, targets: :osd32mp1}, 64 | {:nerves_system_x86_64, "~> 1.13", runtime: false, targets: :x86_64}, 65 | {:nerves_system_grisp2, "~> 0.3", runtime: false, targets: :grisp2}, 66 | {:nerves_system_mangopi_mq_pro, "~> 0.4", runtime: false, targets: :mangopi_mq_pro} 67 | ] 68 | end 69 | 70 | def release do 71 | [ 72 | overwrite: true, 73 | # Erlang distribution is not started automatically. 74 | # See https://hexdocs.pm/nerves_pack/readme.html#erlang-distribution 75 | cookie: "#{@app}_cookie", 76 | include_erts: &Nerves.Release.erts/0, 77 | steps: [&Nerves.Release.init/1, :assemble], 78 | strip_beams: Mix.env() == :prod or [keep: ["Docs"]] 79 | ] 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /hello_erlang/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Add custom options here 2 | 3 | ## Distributed Erlang Options 4 | ## The cookie needs to be configured prior to vm boot for 5 | ## for read only filesystem. 6 | 7 | -setcookie <%= @release.options[:cookie] %> 8 | 9 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 10 | ## break handler and possibly exiting the VM. 11 | +Bc 12 | 13 | # Allow time warps so that the Erlang system time can more closely match the 14 | # OS system time. 15 | +C multi_time_warp 16 | 17 | ## Load code at system startup 18 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 19 | -mode embedded 20 | 21 | # Load code as per the boot script since not using archives 22 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 23 | -code_path_choice strict 24 | 25 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 26 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 27 | +sbwt none 28 | +sbwtdcpu none 29 | +sbwtdio none 30 | 31 | ## Save the shell history between reboots 32 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 33 | -kernel shell_history enabled 34 | 35 | ## Enable heartbeat monitoring of the Erlang runtime system 36 | -heart -env HEART_BEAT_TIMEOUT 30 37 | -------------------------------------------------------------------------------- /hello_erlang/rootfs_overlay/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_erlang/rootfs_overlay/.keep -------------------------------------------------------------------------------- /hello_erlang/src/hello_erlang_app.erl: -------------------------------------------------------------------------------- 1 | -module(hello_erlang_app). 2 | 3 | -behaviour(application). 4 | 5 | -export([start/2, stop/1]). 6 | 7 | -spec start(Type :: normal | {takeover, Node} | 8 | {failover, Node}, 9 | Args :: term()) -> {ok, Pid :: pid()} | 10 | {error, Reason :: term()}. 11 | 12 | start(normal, Options) -> 13 | hello_erlang_supervisor:start_link(Options). 14 | 15 | -spec stop(State :: term()) -> ok. 16 | 17 | stop(_State) -> ok. 18 | -------------------------------------------------------------------------------- /hello_erlang/src/hello_erlang_supervisor.erl: -------------------------------------------------------------------------------- 1 | -module(hello_erlang_supervisor). 2 | 3 | -behaviour(supervisor). 4 | 5 | -export([init/1, start_link/1]). 6 | 7 | start_link(Options) -> 8 | supervisor:start_link({local, ?MODULE}, ?MODULE, 9 | Options). 10 | 11 | init(_Options) -> 12 | Flags = {one_for_one, 1000, 3600}, 13 | Specs = [{hello_erlang_worker, 14 | {hello_erlang_worker, start_link, []}, permanent, 2000, 15 | worker, [hello_erlang_worker]}], 16 | {ok, {Flags, Specs}}. 17 | -------------------------------------------------------------------------------- /hello_erlang/src/hello_erlang_worker.erl: -------------------------------------------------------------------------------- 1 | -module(hello_erlang_worker). 2 | 3 | -behaviour(gen_server). 4 | 5 | -export([code_change/3, handle_call/3, handle_cast/2, 6 | handle_info/2, init/1, start_link/0, terminate/2]). 7 | 8 | start_link() -> 9 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], 10 | []). 11 | 12 | init(_Args) -> hello_me(), {ok, nostate}. 13 | 14 | handle_call(_What, _From, State) -> {reply, ok, State}. 15 | 16 | handle_cast(_What, State) -> {noreply, State}. 17 | 18 | handle_info(hello, State) -> 19 | hello_me(), {noreply, State}. 20 | 21 | terminate(_Reason, _State) -> ok. 22 | 23 | code_change(_OldVersion, State, _Extra) -> {ok, State}. 24 | 25 | hello_me() -> 26 | io:format("Hello!~n"), 27 | erlang:send_after(5000, erlang:self(), hello). 28 | -------------------------------------------------------------------------------- /hello_gpio/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: [ 4 | "{mix,.formatter}.exs", 5 | "{config,lib,test}/**/*.{ex,exs}", 6 | "rootfs_overlay/etc/iex.exs" 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /hello_gpio/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | -------------------------------------------------------------------------------- /hello_gpio/assets/GPIO-input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_gpio/assets/GPIO-input.png -------------------------------------------------------------------------------- /hello_gpio/assets/gpio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_gpio/assets/gpio.png -------------------------------------------------------------------------------- /hello_gpio/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and is restricted to 5 | # this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | config :hello_gpio, target: Mix.target() 12 | 13 | # Customize non-Elixir parts of the firmware. See 14 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 15 | 16 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 17 | 18 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 19 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 20 | 21 | config :nerves, source_date_epoch: "1690656169" 22 | 23 | if Mix.target() == :host do 24 | import_config "host.exs" 25 | else 26 | import_config "target.exs" 27 | end 28 | -------------------------------------------------------------------------------- /hello_gpio/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | -------------------------------------------------------------------------------- /hello_gpio/lib/hello_gpio.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloGpio do 2 | use Application 3 | 4 | require Logger 5 | 6 | alias Circuits.GPIO 7 | 8 | @output_pin Application.compile_env(:hello_gpio, :output_pin, 26) 9 | @input_pin Application.compile_env(:hello_gpio, :input_pin, 20) 10 | 11 | def start(_type, _args) do 12 | Logger.info("Starting pin #{@output_pin} as output") 13 | {:ok, output_gpio} = GPIO.open(@output_pin, :output) 14 | spawn(fn -> toggle_pin_forever(output_gpio) end) 15 | 16 | Logger.info("Starting pin #{@input_pin} as input") 17 | {:ok, input_gpio} = GPIO.open(@input_pin, :input) 18 | spawn(fn -> listen_forever(input_gpio) end) 19 | {:ok, self()} 20 | end 21 | 22 | defp toggle_pin_forever(output_gpio) do 23 | Logger.debug("Turning pin #{@output_pin} ON") 24 | GPIO.write(output_gpio, 1) 25 | Process.sleep(500) 26 | 27 | Logger.debug("Turning pin #{@output_pin} OFF") 28 | GPIO.write(output_gpio, 0) 29 | Process.sleep(500) 30 | 31 | toggle_pin_forever(output_gpio) 32 | end 33 | 34 | defp listen_forever(input_gpio) do 35 | # Start listening for interrupts on rising and falling edges 36 | GPIO.set_interrupts(input_gpio, :both) 37 | listen_loop() 38 | end 39 | 40 | defp listen_loop() do 41 | # Infinite loop receiving interrupts from gpio 42 | receive do 43 | {:circuits_gpio, p, _timestamp, 1} -> 44 | Logger.debug("Received rising event on pin #{p}") 45 | 46 | {:circuits_gpio, p, _timestamp, 0} -> 47 | Logger.debug("Received falling event on pin #{p}") 48 | end 49 | 50 | listen_loop() 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /hello_gpio/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule HelloGpio.MixProject do 2 | use Mix.Project 3 | 4 | @app :hello_gpio 5 | @version "0.1.0" 6 | @all_targets [ 7 | :rpi, 8 | :rpi0, 9 | :rpi2, 10 | :rpi3, 11 | :rpi3a, 12 | :rpi4, 13 | :bbb, 14 | :osd32mp1, 15 | :x86_64, 16 | :grisp2, 17 | :mangopi_mq_pro 18 | ] 19 | 20 | def project do 21 | [ 22 | app: @app, 23 | version: @version, 24 | elixir: "~> 1.16", 25 | archives: [nerves_bootstrap: "~> 1.13"], 26 | start_permanent: Mix.env() == :prod, 27 | deps: deps(), 28 | releases: [{@app, release()}], 29 | preferred_cli_target: [run: :host, test: :host] 30 | ] 31 | end 32 | 33 | # Run "mix help compile.app" to learn about applications. 34 | def application do 35 | [ 36 | mod: {HelloGpio, []}, 37 | extra_applications: [:logger, :runtime_tools] 38 | ] 39 | end 40 | 41 | # Run "mix help deps" to learn about dependencies. 42 | defp deps do 43 | [ 44 | # Dependencies for all targets 45 | {:circuits_gpio, "~> 2.0"}, 46 | {:nerves, "~> 1.10", runtime: false}, 47 | {:shoehorn, "~> 0.9.0"}, 48 | {:ring_logger, "~> 0.11.0"}, 49 | {:toolshed, "~> 0.4.0"}, 50 | 51 | # Dependencies for all targets except :host 52 | {:nerves_runtime, "~> 0.13.0", targets: @all_targets}, 53 | {:nerves_pack, "~> 0.7.0", targets: @all_targets}, 54 | 55 | # Dependencies for specific targets 56 | {:nerves_system_rpi, "~> 1.13", runtime: false, targets: :rpi}, 57 | {:nerves_system_rpi0, "~> 1.13", runtime: false, targets: :rpi0}, 58 | {:nerves_system_rpi2, "~> 1.13", runtime: false, targets: :rpi2}, 59 | {:nerves_system_rpi3, "~> 1.13", runtime: false, targets: :rpi3}, 60 | {:nerves_system_rpi3a, "~> 1.13", runtime: false, targets: :rpi3a}, 61 | {:nerves_system_rpi4, "~> 1.13", runtime: false, targets: :rpi4}, 62 | {:nerves_system_bbb, "~> 2.8", runtime: false, targets: :bbb}, 63 | {:nerves_system_osd32mp1, "~> 0.4", runtime: false, targets: :osd32mp1}, 64 | {:nerves_system_x86_64, "~> 1.13", runtime: false, targets: :x86_64}, 65 | {:nerves_system_grisp2, "~> 0.3", runtime: false, targets: :grisp2}, 66 | {:nerves_system_mangopi_mq_pro, "~> 0.4", runtime: false, targets: :mangopi_mq_pro} 67 | ] 68 | end 69 | 70 | def release do 71 | [ 72 | overwrite: true, 73 | # Erlang distribution is not started automatically. 74 | # See https://hexdocs.pm/nerves_pack/readme.html#erlang-distribution 75 | cookie: "#{@app}_cookie", 76 | include_erts: &Nerves.Release.erts/0, 77 | steps: [&Nerves.Release.init/1, :assemble], 78 | strip_beams: Mix.env() == :prod or [keep: ["Docs"]] 79 | ] 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /hello_gpio/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Customize flags given to the VM: http://erlang.org/doc/man/erl.html 2 | 3 | ## Do not set -name or -sname here. Prefer configuring them at runtime 4 | ## Configure -setcookie in the mix.exs release section or at runtime 5 | 6 | ## Number of dirty schedulers doing IO work (file, sockets, and others) 7 | ##+SDio 5 8 | 9 | ## Increase number of concurrent ports/sockets 10 | ##+Q 65536 11 | 12 | ## Tweak GC to run more often 13 | ##-env ERL_FULLSWEEP_AFTER 10 14 | 15 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 16 | ## break handler and possibly exiting the VM. 17 | +Bc 18 | 19 | # Allow time warps so that the Erlang system time can more closely match the 20 | # OS system time. 21 | +C multi_time_warp 22 | 23 | ## Load code at system startup 24 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 25 | -mode embedded 26 | 27 | # Load code as per the boot script since not using archives 28 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 29 | -code_path_choice strict 30 | 31 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 32 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 33 | +sbwt none 34 | +sbwtdcpu none 35 | +sbwtdio none 36 | 37 | ## Save the shell history between reboots 38 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 39 | -kernel shell_history enabled 40 | 41 | ## Enable heartbeat monitoring of the Erlang runtime system 42 | -heart -env HEART_BEAT_TIMEOUT 30 43 | 44 | ## Start the Elixir shell 45 | 46 | -noshell 47 | -user elixir 48 | -run elixir start_cli 49 | 50 | ## Enable colors in the shell 51 | -elixir ansi_enabled true 52 | 53 | ## Options added after -extra are interpreted as plain arguments and can be 54 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 55 | ## interpreted by Elixir and anything afterwards is left around for other IEx 56 | ## and user applications. 57 | -extra --no-halt 58 | -- 59 | --dot-iex /etc/iex.exs 60 | -------------------------------------------------------------------------------- /hello_gpio/rootfs_overlay/etc/iex.exs: -------------------------------------------------------------------------------- 1 | NervesMOTD.print() 2 | 3 | # Add Toolshed helpers to the IEx session 4 | use Toolshed 5 | -------------------------------------------------------------------------------- /hello_lfe/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /hello_lfe/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | -------------------------------------------------------------------------------- /hello_lfe/README.md: -------------------------------------------------------------------------------- 1 | # HelloLfe 2 | 3 | This demonstrates a simple Nerves project that's written in 4 | [LFE](http://lfe.io/). 5 | 6 | ![terminal window](assets/shell.png) 7 | 8 | ## LFE-izing a Nerves project 9 | 10 | Since LFE is a BEAM language, it works great with Nerves. This example project 11 | is a trivial LFE/Nerves project. Since the Nerves tooling uses Elixir, the top 12 | level project must use Elixir's `mix` build tool, but dependencies can use other 13 | build tools like `rebar3`. 14 | 15 | The following describes what needed to be done after creating a Nerves project 16 | the "normal" Elixir-based way using `mix nerves.new `. 17 | 18 | The first step is to add LFE as a dependency in your `mix.exs`: 19 | 20 | ```elixir 21 | {:lfe, "~> 2.0", compile: "make"}, 22 | ``` 23 | 24 | I've found that it's easiest to have `mix` build LFE source files. The 25 | [mix_lfe](https://github.com/meddle0x53/mix_lfe) was so close to working, but I 26 | had trouble with it (could have been me struggling along the way). To work 27 | around the issue, I copied out the `mix` compiler from it. That's the code in 28 | the `lib` directory. It would be great if someone could work with the `mix_lfe` 29 | maintainer to split the project up so that the compiler piece can be used 30 | separately. 31 | 32 | The next step is to update the `rel/vm.args`. First disable `embedded` mode 33 | since it doesn't seem to work with LFE: 34 | 35 | ```sh 36 | #-mode embedded 37 | ``` 38 | 39 | Then update the `-user` option to start the LFE shell: 40 | 41 | ```sh 42 | -user lfe_init 43 | ``` 44 | 45 | Finally, create a `src` directory and put your LFE code in there like you 46 | would in a normal LFE project. 47 | 48 | ## Building 49 | 50 | Building follows the standard Nerves recipe. Here's an example: 51 | 52 | ```sh 53 | export MIX_TARGET=bbb 54 | mix deps.get 55 | mix firmware 56 | ``` 57 | 58 | And then burn an SD card using `mix firmware.burn`. 59 | 60 | ## Using 61 | 62 | The images pull in the standard `nerves_pack` infrastructure for bringing up 63 | networking and other initialization steps. See the `config/config.exs` for 64 | network parameters and other configuration. 65 | 66 | ## Running 67 | 68 | Attach to the console of your board (i.e., RPi3 is HDMI, BBB/RPi0 is USB 69 | gadget). You should see prints from the application. Note that the first boot 70 | takes quite a bit longer than the rest since it initializes the application's 71 | writable filesystem. 72 | 73 | To see log messages, run: 74 | 75 | ```lfe 76 | (Elixir.RingLogger:next) 77 | ``` 78 | 79 | ## Notes 80 | 81 | The [mix_lfe](https://github.com/meddle0x53/mix_lfe) was so close to working, 82 | but I had trouble with it. To work around the issue, I copied out the `mix` 83 | compiler from it. That's the code in `lib`. 84 | -------------------------------------------------------------------------------- /hello_lfe/assets/shell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_lfe/assets/shell.png -------------------------------------------------------------------------------- /hello_lfe/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | config :hello_lfe, target: Mix.target() 12 | 13 | # Customize non-Elixir parts of the firmware. See 14 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 15 | 16 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 17 | 18 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 19 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 20 | 21 | config :nerves, source_date_epoch: "1577975236" 22 | 23 | # Use Ringlogger as the logger backend and remove :console. 24 | # See https://hexdocs.pm/ring_logger/readme.html for more information on 25 | # configuring ring_logger. 26 | 27 | config :logger, backends: [RingLogger] 28 | 29 | if Mix.target() == :host do 30 | import_config "host.exs" 31 | else 32 | import_config "target.exs" 33 | end 34 | -------------------------------------------------------------------------------- /hello_lfe/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | -------------------------------------------------------------------------------- /hello_lfe/lib/mix/compilers/lfe.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Compilers.Lfe do 2 | alias Mix.Compilers.Erlang, as: ErlangCompiler 3 | 4 | @moduledoc false 5 | 6 | @doc """ 7 | Compiles the files in `mappings` with '.lfe' extensions into 8 | the destinations. 9 | Does this for each stale input and output pair (or for all if `force` is `true`) and 10 | removes files that no longer have a source, while keeping the `manifest` up to date. 11 | 12 | `mappings` should be a list of tuples in the form of `{src, dest}` paths. 13 | 14 | Uses an [idea](https://github.com/elixir-lang/elixir/blob/e1c903a5956e4cb9075f0aac00638145788b0da4/lib/mix/lib/mix/compilers/erlang.ex#L20) from the Erlang Mix compiler to do so. 15 | 16 | It supports the options of the Erlang Mix compiler under the covers as it is used. 17 | """ 18 | def compile(manifest, [{_, _} | _] = mappings, opts) do 19 | callback = fn input, output -> 20 | module = input |> Path.basename(".lfe") |> String.to_atom() 21 | :code.purge(module) 22 | :code.delete(module) 23 | 24 | outdir = output |> Path.dirname() |> ErlangCompiler.to_erl_file() 25 | 26 | compile_result( 27 | :lfe_comp.file(ErlangCompiler.to_erl_file(input), [{:outdir, outdir}, :return, :report]) 28 | ) 29 | end 30 | 31 | ErlangCompiler.compile(manifest, mappings, :lfe, :beam, opts, callback) 32 | end 33 | 34 | @doc """ 35 | Removes compiled files for the given `manifest`. 36 | """ 37 | def clean(manifest), do: ErlangCompiler.clean(manifest) 38 | 39 | defp compile_result({:error, [{:error, [{file, [error | _]}], []}], [], []}) do 40 | {:error, [{file, [error]}], []} 41 | end 42 | 43 | defp compile_result(result), do: result 44 | end 45 | -------------------------------------------------------------------------------- /hello_lfe/lib/mix/tasks/compile.lfe.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Compile.Lfe do 2 | use Mix.Task.Compiler 3 | import Mix.Compilers.Lfe 4 | 5 | @recursive true 6 | @manifest "compile.lfe" 7 | @switches [force: :boolean, all_warnings: :boolean] 8 | 9 | @moduledoc """ 10 | Compiles LFE source files. 11 | 12 | Uses an [idea](https://github.com/elixir-lang/elixir/blob/e1c903a5956e4cb9075f0aac00638145788b0da4/lib/mix/lib/mix/compilers/erlang.ex#L20) from the Erlang Mix compiler to do so. 13 | 14 | These options are supported: 15 | 16 | ## Command line options 17 | * `--force` - forces compilation regardless of modification times 18 | * `--all-warnings` - prints warnings even from files that do not need to be recompiled 19 | 20 | ## Configuration 21 | 22 | The [Erlang compiler configuration](https://github.com/elixir-lang/elixir/blob/master/lib/mix/lib/mix/tasks/compile.erlang.ex#L31) is supported. 23 | Specific configuration options for the LFE compiler will be supported in future. 24 | """ 25 | 26 | @doc """ 27 | Runs this task. 28 | """ 29 | def run(args) do 30 | {opts, _, _} = OptionParser.parse(args, switches: @switches) 31 | do_run(opts) 32 | end 33 | 34 | defp do_run(opts) do 35 | dest = Mix.Project.compile_path() 36 | 37 | compile(manifest(), [{"src", dest}], opts) 38 | end 39 | 40 | @doc """ 41 | Returns LFE manifests. 42 | """ 43 | def manifests, do: [manifest()] 44 | 45 | @doc """ 46 | Cleans up compilation artifacts. 47 | """ 48 | def clean, do: clean(manifest()) 49 | 50 | defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest) 51 | end 52 | -------------------------------------------------------------------------------- /hello_lfe/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Add custom options here 2 | 3 | ## Distributed Erlang Options 4 | ## The cookie needs to be configured prior to vm boot for 5 | ## for read only filesystem. 6 | 7 | -setcookie <%= @release.options[:cookie] %> 8 | 9 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 10 | ## break handler and possibly exiting the VM. 11 | +Bc 12 | 13 | # Allow time warps so that the Erlang system time can more closely match the 14 | # OS system time. 15 | +C multi_time_warp 16 | 17 | ## Load code at system startup 18 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 19 | 20 | # NOTE: LFE doesn't like embedded mode. If you enable, you'll get this: 21 | # 22 | # lfe> (help) 23 | # exception error: no function clause matching 24 | # in (lfe_macro : format_error undef) 25 | # in lfe_shell:-list_ews/2-fun-0-/2 (src/lfe_shell.erl, line 441) 26 | # in lists:foreach/2 (lists.erl, line 1338) 27 | 28 | #-mode embedded 29 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 30 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 31 | +sbwt none 32 | +sbwtdcpu none 33 | +sbwtdio none 34 | 35 | ## Save the shell history between reboots 36 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 37 | -kernel shell_history enabled 38 | 39 | ## Enable heartbeat monitoring of the Erlang runtime system 40 | -heart -env HEART_BEAT_TIMEOUT 30 41 | 42 | ## Start the LFE shell 43 | -user lfe_init 44 | 45 | -------------------------------------------------------------------------------- /hello_lfe/rootfs_overlay/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_lfe/rootfs_overlay/.keep -------------------------------------------------------------------------------- /hello_lfe/rootfs_overlay/etc/.placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_lfe/rootfs_overlay/etc/.placeholder -------------------------------------------------------------------------------- /hello_lfe/src/hello-sup.lfe: -------------------------------------------------------------------------------- 1 | (defmodule hello-sup 2 | "The root supervisor of the `hello` application." 3 | (behaviour supervisor) 4 | (export (start_link 0) (init 1))) 5 | 6 | (defun start_link () 7 | "Starts the supervisor process." 8 | (supervisor:start_link 9 | (tuple 'local 'hello-sup) 'hello-sup (list))) 10 | 11 | (defun init (args) 12 | " 13 | Initializes the supervisor process, by providing it with a specification. 14 | This supervisor has only one worker process - the `hello-worker`. 15 | " 16 | (let* ((flags (tuple 'one_for_one 1000 3600)) 17 | (specs (list 18 | (tuple 'hello-worker 19 | (tuple 'hello-worker 'start_link (list)) 20 | 'permanent 21 | 2000 22 | 'worker 23 | (list 'hello-worker))))) 24 | (tuple 'ok (tuple flags specs)))) 25 | -------------------------------------------------------------------------------- /hello_lfe/src/hello-worker.lfe: -------------------------------------------------------------------------------- 1 | (defmodule hello-worker 2 | (behaviour gen_server) 3 | (export (start_link 0) (init 1) (handle_info 2) (terminate 2) (code_change 3))) 4 | 5 | (defun start_link () 6 | (gen_server:start_link (tuple 'local 'hello-worker) 'hello-worker (list) (list))) 7 | 8 | (defun init (args) 9 | (hello_me) 10 | (tuple 'ok 'nostate)) 11 | 12 | (defun handle_info (info state) 13 | (hello_me) 14 | (tuple 'noreply state)) 15 | 16 | (defun terminate (reason state) 17 | 'ok) 18 | 19 | (defun code_change (old-vers state extra) 20 | (tuple 'ok state)) 21 | 22 | (defun hello_me () 23 | (io:format '"Hello!~n") 24 | (erlang:send_after 5000 (erlang:self) 'hello)) 25 | -------------------------------------------------------------------------------- /hello_lfe/src/hello_lfe.lfe: -------------------------------------------------------------------------------- 1 | (defmodule hello_lfe 2 | (behaviour application) 3 | (export all)) 4 | 5 | (defun start (type, args) 6 | (io:format '"Starting application...~n") 7 | (hello-sup:start_link)) 8 | 9 | (defun stop (state) 10 | 'ok) 11 | 12 | -------------------------------------------------------------------------------- /hello_live_view/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:phoenix], 3 | plugins: [Phoenix.LiveView.HTMLFormatter], 4 | inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] 5 | ] 6 | -------------------------------------------------------------------------------- /hello_live_view/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Temporary files, for example, from tests. 23 | /tmp/ 24 | 25 | # Ignore package tarball (built via "mix hex.build"). 26 | hello_live_view-*.tar 27 | 28 | # Ignore assets that are produced by build tools. 29 | /priv/static/assets/ 30 | 31 | # Ignore digested assets cache. 32 | /priv/static/cache_manifest.json 33 | 34 | # In case you use Node.js/npm, you want to ignore these. 35 | npm-debug.log 36 | /assets/node_modules/ 37 | 38 | .DS_Store 39 | -------------------------------------------------------------------------------- /hello_live_view/assets/css/app.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; 4 | 5 | /* This file is for your main application CSS */ 6 | -------------------------------------------------------------------------------- /hello_live_view/assets/js/app.js: -------------------------------------------------------------------------------- 1 | // If you want to use Phoenix channels, run `mix help phx.gen.channel` 2 | // to get started and then uncomment the line below. 3 | // import "./user_socket.js" 4 | 5 | // You can include dependencies in two ways. 6 | // 7 | // The simplest option is to put them in assets/vendor and 8 | // import them using relative paths: 9 | // 10 | // import "../vendor/some-package.js" 11 | // 12 | // Alternatively, you can `npm install some-package --prefix assets` and import 13 | // them using a path starting with the package name: 14 | // 15 | // import "some-package" 16 | // 17 | 18 | // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. 19 | import "phoenix_html" 20 | // Establish Phoenix Socket and LiveView configuration. 21 | import {Socket} from "phoenix" 22 | import {LiveSocket} from "phoenix_live_view" 23 | import topbar from "../vendor/topbar" 24 | 25 | let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") 26 | let liveSocket = new LiveSocket("/live", Socket, { 27 | longPollFallbackMs: 2500, 28 | params: {_csrf_token: csrfToken} 29 | }) 30 | 31 | // Show progress bar on live navigation and form submits 32 | topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) 33 | window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) 34 | window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) 35 | 36 | // connect if there are any LiveViews on the page 37 | liveSocket.connect() 38 | 39 | // expose liveSocket on window for web console debug logs and latency simulation: 40 | // >> liveSocket.enableDebug() 41 | // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session 42 | // >> liveSocket.disableLatencySim() 43 | window.liveSocket = liveSocket 44 | 45 | -------------------------------------------------------------------------------- /hello_live_view/config/dev.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For development, we disable any cache and enable 4 | # debugging and code reloading. 5 | # 6 | # The watchers configuration can be used to run external 7 | # watchers to your application. For example, we use it 8 | # with esbuild to bundle .js and .css sources. 9 | config :hello_live_view, HelloLiveViewWeb.Endpoint, 10 | # Binding to loopback ipv4 address prevents access from other machines. 11 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. 12 | http: [ip: {127, 0, 0, 1}, port: 4000], 13 | check_origin: false, 14 | code_reloader: true, 15 | debug_errors: true, 16 | secret_key_base: "sODMOCqDOBh6ykWCXcIW3Y19hDlM4b8Y99/ExyZPE6OeBEc7z+3FgbXlM/kqW2Vm", 17 | watchers: [ 18 | esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}, 19 | tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} 20 | ] 21 | 22 | # ## SSL Support 23 | # 24 | # In order to use HTTPS in development, a self-signed 25 | # certificate can be generated by running the following 26 | # Mix task: 27 | # 28 | # mix phx.gen.cert 29 | # 30 | # Run `mix help phx.gen.cert` for more information. 31 | # 32 | # The `http:` config above can be replaced with: 33 | # 34 | # https: [ 35 | # port: 4001, 36 | # cipher_suite: :strong, 37 | # keyfile: "priv/cert/selfsigned_key.pem", 38 | # certfile: "priv/cert/selfsigned.pem" 39 | # ], 40 | # 41 | # If desired, both `http:` and `https:` keys can be 42 | # configured to run both http and https servers on 43 | # different ports. 44 | 45 | # Watch static and templates for browser reloading. 46 | config :hello_live_view, HelloLiveViewWeb.Endpoint, 47 | live_reload: [ 48 | patterns: [ 49 | ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", 50 | ~r"priv/gettext/.*(po)$", 51 | ~r"lib/hello_live_view_web/(controllers|live|components)/.*(ex|heex)$" 52 | ] 53 | ] 54 | 55 | # Enable dev routes for dashboard and mailbox 56 | config :hello_live_view, dev_routes: true 57 | 58 | # Do not include metadata nor timestamps in development logs 59 | config :logger, :console, format: "[$level] $message\n" 60 | 61 | # Set a higher stacktrace during development. Avoid configuring such 62 | # in production as building large stacktraces may be expensive. 63 | config :phoenix, :stacktrace_depth, 20 64 | 65 | # Initialize plugs at runtime for faster development compilation 66 | config :phoenix, :plug_init_mode, :runtime 67 | -------------------------------------------------------------------------------- /hello_live_view/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | -------------------------------------------------------------------------------- /hello_live_view/config/prod.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For production, don't forget to configure the url host 4 | # to something meaningful, Phoenix uses this information 5 | # when generating URLs. 6 | 7 | # Note we also include the path to a cache manifest 8 | # containing the digested version of static files. This 9 | # manifest is generated by the `mix assets.deploy` task, 10 | # which you should run after static files are built and 11 | # before starting your production server. 12 | config :hello_live_view, HelloLiveViewWeb.Endpoint, 13 | cache_static_manifest: "priv/static/cache_manifest.json" 14 | 15 | # Do not print debug messages in production 16 | config :logger, level: :info 17 | 18 | # Runtime production configuration, including reading 19 | # of environment variables, is done on config/runtime.exs. 20 | -------------------------------------------------------------------------------- /hello_live_view/config/test.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # We don't run a server during test. If one is required, 4 | # you can enable the server option below. 5 | config :hello_live_view, HelloLiveViewWeb.Endpoint, 6 | http: [ip: {127, 0, 0, 1}, port: 4002], 7 | secret_key_base: "skPYVgOS63ZmbyfKrcf4OwImk+OQiYt/I5fCzPvFzMIeg2vq1HYvNxAyIkntZVKk", 8 | server: false 9 | 10 | # Print only warnings and errors during test 11 | config :logger, level: :warning 12 | 13 | # Initialize plugs at runtime for faster test compilation 14 | config :phoenix, :plug_init_mode, :runtime 15 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveView do 2 | @moduledoc """ 3 | HelloLiveView keeps the contexts that define your domain 4 | and business logic. 5 | 6 | Contexts are also responsible for managing your data, regardless 7 | if it comes from the database, an external API or others. 8 | """ 9 | end 10 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view/application.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveView.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = 11 | [ 12 | # Start the Telemetry supervisor 13 | HelloLiveViewWeb.Telemetry, 14 | # Start the PubSub system 15 | {Phoenix.PubSub, name: HelloLiveView.PubSub}, 16 | # Start the Endpoint (http/https) 17 | HelloLiveViewWeb.Endpoint 18 | # Start a worker by calling: HelloLiveView.Worker.start_link(arg) 19 | # {HelloLiveView.Worker, arg} 20 | ] ++ children(target()) 21 | 22 | # See https://hexdocs.pm/elixir/Supervisor.html 23 | # for other strategies and supported options 24 | opts = [strategy: :one_for_one, name: HelloLiveView.Supervisor] 25 | Supervisor.start_link(children, opts) 26 | end 27 | 28 | # List all child processes to be supervised 29 | def children(:host) do 30 | [ 31 | # Children that only run on the host 32 | # Starts a worker by calling: HelloLiveView.Worker.start_link(arg) 33 | # {HelloLiveView.Worker, arg}, 34 | ] 35 | end 36 | 37 | def children(_target) do 38 | [ 39 | # Children for all targets except host 40 | # Starts a worker by calling: HelloLiveView.Worker.start_link(arg) 41 | # {HelloLiveView.Worker, arg}, 42 | ] 43 | end 44 | 45 | def target() do 46 | Application.get_env(:hello_live_view, :target) 47 | end 48 | 49 | # Tell Phoenix to update the endpoint configuration 50 | # whenever the application is updated. 51 | @impl true 52 | def config_change(changed, _new, removed) do 53 | HelloLiveViewWeb.Endpoint.config_change(changed, removed) 54 | :ok 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/components/layouts.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.Layouts do 2 | use HelloLiveViewWeb, :html 3 | 4 | embed_templates "layouts/*" 5 | end 6 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/components/layouts/app.html.heex: -------------------------------------------------------------------------------- 1 |
2 | 22 | 23 | {@inner_content} 24 |
25 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/components/layouts/root.html.heex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <.live_title suffix=" · Phoenix Framework"> 8 | {assigns[:page_title] || "HelloLiveView"} 9 | 10 | 11 | 13 | 14 | 15 | {@inner_content} 16 | 17 | 18 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/components/text.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.Components.Text do 2 | use Phoenix.Component 3 | 4 | def page_title(assigns) do 5 | ~H""" 6 |
7 |
8 |

{@title}

9 |
10 |
11 | """ 12 | end 13 | 14 | def body(assigns) do 15 | ~H""" 16 |
17 | {render_slot(@inner_block)} 18 |
19 | """ 20 | end 21 | 22 | def link_to(assigns) do 23 | ~H""" 24 | {render_slot(@inner_block)} 25 | """ 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/controllers/error_html.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.ErrorHTML do 2 | use HelloLiveViewWeb, :html 3 | 4 | # If you want to customize your error pages, 5 | # uncomment the embed_templates/1 call below 6 | # and add pages to the error directory: 7 | # 8 | # * lib/hello_live_view_web/controllers/error_html/404.html.heex 9 | # * lib/hello_live_view_web/controllers/error_html/500.html.heex 10 | # 11 | # embed_templates "error_html/*" 12 | 13 | # The default is to render a plain text page based on 14 | # the template name. For example, "404.html" becomes 15 | # "Not Found". 16 | def render(template, _assigns) do 17 | Phoenix.Controller.status_message_from_template(template) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/controllers/error_json.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.ErrorJSON do 2 | # If you want to customize a particular status code, 3 | # you may add your own clauses, such as: 4 | # 5 | # def render("500.json", _assigns) do 6 | # %{errors: %{detail: "Internal Server Error"}} 7 | # end 8 | 9 | # By default, Phoenix returns the status message from 10 | # the template name. For example, "404.json" becomes 11 | # "Not Found". 12 | def render(template, _assigns) do 13 | %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :hello_live_view 3 | 4 | # The session will be stored in the cookie and signed, 5 | # this means its contents can be read but not tampered with. 6 | # Set :encryption_salt if you would also like to encrypt it. 7 | @session_options [ 8 | store: :cookie, 9 | key: "_hello_live_view_key", 10 | signing_salt: "jtTHK+Go", 11 | same_site: "Lax" 12 | ] 13 | 14 | socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] 15 | 16 | # Serve at "/" the static files from "priv/static" directory. 17 | # 18 | # You should set gzip to true if you are running phx.digest 19 | # when deploying your static files in production. 20 | plug Plug.Static, 21 | at: "/", 22 | from: :hello_live_view, 23 | gzip: false, 24 | only: HelloLiveViewWeb.static_paths() 25 | 26 | # Code reloading can be explicitly enabled under the 27 | # :code_reloader configuration of your endpoint. 28 | if code_reloading? do 29 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 30 | plug Phoenix.LiveReloader 31 | plug Phoenix.CodeReloader 32 | end 33 | 34 | plug Phoenix.LiveDashboard.RequestLogger, 35 | param_key: "request_logger", 36 | cookie_key: "request_logger" 37 | 38 | plug Plug.RequestId 39 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 40 | 41 | plug Plug.Parsers, 42 | parsers: [:urlencoded, :multipart, :json], 43 | pass: ["*/*"], 44 | json_decoder: Phoenix.json_library() 45 | 46 | plug Plug.MethodOverride 47 | plug Plug.Head 48 | plug Plug.Session, @session_options 49 | plug HelloLiveViewWeb.Router 50 | end 51 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.Gettext do 2 | @moduledoc """ 3 | A module providing Internationalization with a gettext-based API. 4 | 5 | By using [Gettext](https://hexdocs.pm/gettext), 6 | your module gains a set of macros for translations, for example: 7 | 8 | import HelloLiveViewWeb.Gettext 9 | 10 | # Simple translation 11 | gettext("Here is the string to translate") 12 | 13 | # Plural translation 14 | ngettext("Here is the string to translate", 15 | "Here are the strings to translate", 16 | 3) 17 | 18 | # Domain-based translation 19 | dgettext("errors", "Here is the error message to translate") 20 | 21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. 22 | """ 23 | use Gettext, otp_app: :hello_live_view 24 | end 25 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/live/home.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.Home do 2 | use HelloLiveViewWeb, :live_view 3 | 4 | @impl true 5 | def mount(_params, _session, socket) do 6 | {:ok, socket} 7 | end 8 | 9 | @impl true 10 | def render(assigns) do 11 | ~H""" 12 | <.page_title title="Hello, LiveView!" /> 13 | <.body> 14 | Click to learn more about 15 | <.link_to href="https://nerves-project.org">Nerves 16 | and 17 | <.link_to href="https://www.phoenixframework.org/">LiveView. 18 | 19 | """ 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.Router do 2 | use HelloLiveViewWeb, :router 3 | 4 | pipeline :browser do 5 | plug :accepts, ["html"] 6 | plug :fetch_session 7 | plug :fetch_live_flash 8 | plug :put_root_layout, {HelloLiveViewWeb.Layouts, :root} 9 | plug :protect_from_forgery 10 | plug :put_secure_browser_headers 11 | end 12 | 13 | pipeline :api do 14 | plug :accepts, ["json"] 15 | end 16 | 17 | scope "/", HelloLiveViewWeb do 18 | pipe_through :browser 19 | 20 | live "/", Home 21 | end 22 | 23 | # Other scopes may use custom stacks. 24 | # scope "/api", HelloLiveViewWeb do 25 | # pipe_through :api 26 | # end 27 | 28 | # Enable LiveDashboard in development 29 | if Application.compile_env(:hello_live_view, :dev_routes) do 30 | # If you want to use the LiveDashboard in production, you should put 31 | # it behind authentication and allow only admins to access it. 32 | # If your application does not have an admins-only section yet, 33 | # you can use Plug.BasicAuth to set up some basic authentication 34 | # as long as you are also using SSL (which you should anyway). 35 | import Phoenix.LiveDashboard.Router 36 | 37 | scope "/dev" do 38 | pipe_through :browser 39 | 40 | live_dashboard "/dashboard", metrics: HelloLiveViewWeb.Telemetry 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/telemetry.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.Telemetry do 2 | use Supervisor 3 | import Telemetry.Metrics 4 | 5 | def start_link(arg) do 6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__) 7 | end 8 | 9 | @impl true 10 | def init(_arg) do 11 | children = [ 12 | # Telemetry poller will execute the given period measurements 13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics 14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} 15 | # Add reporters as children of your supervision tree. 16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} 17 | ] 18 | 19 | Supervisor.init(children, strategy: :one_for_one) 20 | end 21 | 22 | def metrics do 23 | [ 24 | # Phoenix Metrics 25 | summary("phoenix.endpoint.start.system_time", 26 | unit: {:native, :millisecond} 27 | ), 28 | summary("phoenix.endpoint.stop.duration", 29 | unit: {:native, :millisecond} 30 | ), 31 | summary("phoenix.router_dispatch.start.system_time", 32 | tags: [:route], 33 | unit: {:native, :millisecond} 34 | ), 35 | summary("phoenix.router_dispatch.exception.duration", 36 | tags: [:route], 37 | unit: {:native, :millisecond} 38 | ), 39 | summary("phoenix.router_dispatch.stop.duration", 40 | tags: [:route], 41 | unit: {:native, :millisecond} 42 | ), 43 | summary("phoenix.socket_connected.duration", 44 | unit: {:native, :millisecond} 45 | ), 46 | summary("phoenix.channel_join.duration", 47 | unit: {:native, :millisecond} 48 | ), 49 | summary("phoenix.channel_handled_in.duration", 50 | tags: [:event], 51 | unit: {:native, :millisecond} 52 | ), 53 | 54 | # VM Metrics 55 | summary("vm.memory.total", unit: {:byte, :kilobyte}), 56 | summary("vm.total_run_queue_lengths.total"), 57 | summary("vm.total_run_queue_lengths.cpu"), 58 | summary("vm.total_run_queue_lengths.io") 59 | ] 60 | end 61 | 62 | defp periodic_measurements do 63 | [ 64 | # A module, function and arguments to be invoked periodically. 65 | # This function must call :telemetry.execute/3 and a metric must be added above. 66 | # {HelloLiveViewWeb, :count_users, []} 67 | ] 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/templates/layout/app.html.heex: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | {@inner_content} 5 |
6 | -------------------------------------------------------------------------------- /hello_live_view/lib/hello_live_view_web/templates/layout/root.html.heex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {live_title_tag(assigns[:page_title] || "Home", suffix: " · Nerves")} 9 | 10 | 17 | 18 | 19 | {@inner_content} 20 | 21 | 22 | -------------------------------------------------------------------------------- /hello_live_view/priv/gettext/en/LC_MESSAGES/errors.po: -------------------------------------------------------------------------------- 1 | ## `msgid`s in this file come from POT (.pot) files. 2 | ## 3 | ## Do not add, change, or remove `msgid`s manually here as 4 | ## they're tied to the ones in the corresponding POT file 5 | ## (with the same domain). 6 | ## 7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge` 8 | ## to merge POT files into PO files. 9 | msgid "" 10 | msgstr "" 11 | "Language: en\n" 12 | -------------------------------------------------------------------------------- /hello_live_view/priv/gettext/errors.pot: -------------------------------------------------------------------------------- 1 | ## This is a PO Template file. 2 | ## 3 | ## `msgid`s here are often extracted from source code. 4 | ## Add new translations manually only if they're dynamic 5 | ## translations that can't be statically extracted. 6 | ## 7 | ## Run `mix gettext.extract` to bring this file up to 8 | ## date. Leave `msgstr`s empty as changing them here has no 9 | ## effect: edit them in PO (`.po`) files instead. 10 | 11 | -------------------------------------------------------------------------------- /hello_live_view/priv/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_live_view/priv/static/favicon.ico -------------------------------------------------------------------------------- /hello_live_view/priv/static/images/nerves.svg: -------------------------------------------------------------------------------- 1 | 3 | Nerves 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /hello_live_view/priv/static/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /hello_live_view/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Customize flags given to the VM: http://erlang.org/doc/man/erl.html 2 | 3 | ## Do not set -name or -sname here. Prefer configuring them at runtime 4 | ## Configure -setcookie in the mix.exs release section or at runtime 5 | 6 | ## Number of dirty schedulers doing IO work (file, sockets, and others) 7 | ##+SDio 5 8 | 9 | ## Increase number of concurrent ports/sockets 10 | ##+Q 65536 11 | 12 | ## Tweak GC to run more often 13 | ##-env ERL_FULLSWEEP_AFTER 10 14 | 15 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 16 | ## break handler and possibly exiting the VM. 17 | +Bc 18 | 19 | # Allow time warps so that the Erlang system time can more closely match the 20 | # OS system time. 21 | +C multi_time_warp 22 | 23 | ## Load code at system startup 24 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 25 | -mode embedded 26 | 27 | # Load code as per the boot script since not using archives 28 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 29 | -code_path_choice strict 30 | 31 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 32 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 33 | +sbwt none 34 | +sbwtdcpu none 35 | +sbwtdio none 36 | 37 | ## Save the shell history between reboots 38 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 39 | -kernel shell_history enabled 40 | 41 | ## Enable heartbeat monitoring of the Erlang runtime system 42 | -heart -env HEART_BEAT_TIMEOUT 30 43 | 44 | ## Start the Elixir shell 45 | 46 | -noshell 47 | -user elixir 48 | -run elixir start_cli 49 | 50 | ## Enable colors in the shell 51 | -elixir ansi_enabled true 52 | 53 | ## Options added after -extra are interpreted as plain arguments and can be 54 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 55 | ## interpreted by Elixir and anything afterwards is left around for other IEx 56 | ## and user applications. 57 | -extra --no-halt 58 | -- 59 | --dot-iex /etc/iex.exs 60 | -------------------------------------------------------------------------------- /hello_live_view/rootfs_overlay/etc/iex.exs: -------------------------------------------------------------------------------- 1 | NervesMOTD.print() 2 | 3 | # Add Toolshed helpers to the IEx session 4 | use Toolshed 5 | -------------------------------------------------------------------------------- /hello_live_view/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | 5 | export MIX_ENV=prod 6 | export MIX_TARGET=rpi3 7 | export HOST=hello_live_view.local 8 | 9 | MIX_TARGET=host mix do deps.get, assets.deploy 10 | mix deps.get 11 | mix firmware 12 | mix upload $HOST 13 | -------------------------------------------------------------------------------- /hello_live_view/test/hello_live_view_web/controllers/error_html_test.exs: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.ErrorHTMLTest do 2 | use HelloLiveViewWeb.ConnCase, async: true 3 | 4 | # Bring render_to_string/4 for testing custom views 5 | import Phoenix.Template 6 | 7 | test "renders 404.html" do 8 | assert render_to_string(HelloLiveViewWeb.ErrorHTML, "404", "html", []) == "Not Found" 9 | end 10 | 11 | test "renders 500.html" do 12 | assert render_to_string(HelloLiveViewWeb.ErrorHTML, "500", "html", []) == 13 | "Internal Server Error" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /hello_live_view/test/hello_live_view_web/controllers/error_json_test.exs: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.ErrorJSONTest do 2 | use HelloLiveViewWeb.ConnCase, async: true 3 | 4 | test "renders 404" do 5 | assert HelloLiveViewWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} 6 | end 7 | 8 | test "renders 500" do 9 | assert HelloLiveViewWeb.ErrorJSON.render("500.json", %{}) == 10 | %{errors: %{detail: "Internal Server Error"}} 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /hello_live_view/test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloLiveViewWeb.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | Such tests rely on `Phoenix.ConnTest` and also 7 | import other functionality to make it easier 8 | to build common data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | we enable the SQL sandbox, so changes done to the database 12 | are reverted at the end of every test. If you are using 13 | PostgreSQL, you can even run database tests asynchronously 14 | by setting `use HelloLiveViewWeb.ConnCase, async: true`, although 15 | this option is not recommended for other databases. 16 | """ 17 | 18 | use ExUnit.CaseTemplate 19 | 20 | using do 21 | quote do 22 | # The default endpoint for testing 23 | @endpoint HelloLiveViewWeb.Endpoint 24 | 25 | use HelloLiveViewWeb, :verified_routes 26 | 27 | # Import conveniences for testing with connections 28 | import Plug.Conn 29 | import Phoenix.ConnTest 30 | import HelloLiveViewWeb.ConnCase 31 | end 32 | end 33 | 34 | setup _tags do 35 | {:ok, conn: Phoenix.ConnTest.build_conn()} 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /hello_live_view/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /hello_scenic/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /hello_scenic/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | foo-*.tar 24 | 25 | 26 | # Ignore scripts marked as secret - usually passwords and such in config files 27 | *.secret.exs 28 | *.secrets.exs 29 | -------------------------------------------------------------------------------- /hello_scenic/.skip-bbb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_scenic/.skip-bbb -------------------------------------------------------------------------------- /hello_scenic/.skip-grisp2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_scenic/.skip-grisp2 -------------------------------------------------------------------------------- /hello_scenic/.skip-osd32mp1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_scenic/.skip-osd32mp1 -------------------------------------------------------------------------------- /hello_scenic/.skip-x86_64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_scenic/.skip-x86_64 -------------------------------------------------------------------------------- /hello_scenic/README.md: -------------------------------------------------------------------------------- 1 | # Hello Scenic 2 | 3 | This example demonstrates a basic project using [Scenic](https://github.com/ScenicFramework/scenic) using the [scenic_driver_local](https://github.com/ScenicFramework/scenic_driver_local) backend to display scenes on a local display of a target device or to a window on the host. 4 | 5 | The example has a [scene](https://github.com/nerves-project/nerves-examples/blob/main/hello_scenic/lib/scenes/home.ex) that just draws a couple of basic shapes, shows an analog clock, "Hello" and "World", and the cursor position, button and scroll events. 6 | 7 | ![Hello World](https://github.com/nerves-project/nerves-examples/blob/main/hello_scenic/helloworld.png?raw=true) 8 | 9 | For a more complete example of the features available in Scenic, see [hello_scenic_full](https://github.com/axelson/hello_scenic_full) 10 | -------------------------------------------------------------------------------- /hello_scenic/assets/readme.txt: -------------------------------------------------------------------------------- 1 | assets_readme.txt -------------------------------------------------------------------------------- /hello_scenic/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | import Config 4 | 5 | # connect the app's asset module to Scenic 6 | config :scenic, :assets, module: HelloScenic.Assets 7 | 8 | # Configure the main viewport for the Scenic application 9 | config :hello_scenic, :viewport, %{ 10 | name: :main_viewport, 11 | size: {320, 240}, 12 | theme: :dark, 13 | default_scene: HelloScenic.Scene.Home, 14 | drivers: [ 15 | [ 16 | module: Scenic.Driver.Local, 17 | name: :local, 18 | window: [resizeable: false, title: "hello_scenic"], 19 | on_close: :stop_system 20 | ] 21 | ] 22 | } 23 | 24 | # Enable the Nerves integration with Mix 25 | Application.start(:nerves_bootstrap) 26 | 27 | config :hello_scenic, target: Mix.target() 28 | 29 | # Customize non-Elixir parts of the firmware. See 30 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 31 | 32 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 33 | 34 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 35 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 36 | 37 | config :nerves, source_date_epoch: "1695093928" 38 | 39 | # It is also possible to import configuration files, relative to this 40 | # directory. For example, you can emulate configuration per environment 41 | # by uncommenting the line below and defining dev.exs, test.exs and such. 42 | # Configuration from the imported file will override the ones defined 43 | # here (which is why it is important to import them last). 44 | # 45 | # import_config "prod.exs" 46 | 47 | if Mix.target() == :host do 48 | import_config "host.exs" 49 | else 50 | import_config "target.exs" 51 | end 52 | -------------------------------------------------------------------------------- /hello_scenic/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | 5 | config :nerves_runtime, 6 | kv_backend: 7 | {Nerves.Runtime.KVBackend.InMemory, 8 | contents: %{ 9 | # The KV store on Nerves systems is typically read from UBoot-env, but 10 | # this allows us to use a pre-populated InMemory store when running on 11 | # host for development and testing. 12 | # 13 | # https://hexdocs.pm/nerves_runtime/readme.html#using-nerves_runtime-in-tests 14 | # https://hexdocs.pm/nerves_runtime/readme.html#nerves-system-and-firmware-metadata 15 | 16 | "nerves_fw_active" => "a", 17 | "a.nerves_fw_architecture" => "generic", 18 | "a.nerves_fw_description" => "N/A", 19 | "a.nerves_fw_platform" => "host", 20 | "a.nerves_fw_version" => "0.0.0" 21 | }} 22 | -------------------------------------------------------------------------------- /hello_scenic/helloworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_scenic/helloworld.png -------------------------------------------------------------------------------- /hello_scenic/lib/assets.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloScenic.Assets do 2 | use Scenic.Assets.Static, 3 | otp_app: :hello_scenic 4 | end 5 | -------------------------------------------------------------------------------- /hello_scenic/lib/components/readme.txt: -------------------------------------------------------------------------------- 1 | comp_readme.txt -------------------------------------------------------------------------------- /hello_scenic/lib/hello_scenic.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloScenic do 2 | @moduledoc """ 3 | Starter application using the Scenic framework. 4 | """ 5 | 6 | def start(_type, _args) do 7 | # load the viewport configuration from config 8 | main_viewport_config = Application.get_env(:hello_scenic, :viewport) 9 | 10 | # start the application with the viewport 11 | children = [ 12 | {Scenic, [main_viewport_config]}, 13 | HelloScenic.PubSub.Supervisor 14 | ] 15 | 16 | Supervisor.start_link(children, strategy: :one_for_one) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /hello_scenic/lib/pubsub/readme.txt: -------------------------------------------------------------------------------- 1 | sens_readme.txt -------------------------------------------------------------------------------- /hello_scenic/lib/pubsub/supervisor.ex: -------------------------------------------------------------------------------- 1 | # a simple supervisor that starts up the Scenic.SensorPubSub server 2 | # and any set of other sensor processes 3 | 4 | defmodule HelloScenic.PubSub.Supervisor do 5 | use Supervisor 6 | 7 | def start_link(_) do 8 | Supervisor.start_link(__MODULE__, :ok) 9 | end 10 | 11 | def init(:ok) do 12 | [ 13 | # add your data publishers here 14 | ] 15 | |> Supervisor.init(strategy: :one_for_one) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /hello_scenic/lib/scenes/home.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloScenic.Scene.Home do 2 | use Scenic.Scene 3 | alias Scenic.Graph 4 | import Scenic.Primitives 5 | import Scenic.Clock.Components 6 | 7 | @graph Graph.build(font: :roboto, font_size: 15) 8 | |> circle(30, fill: :red, stroke: {6, :yellow}, t: {40, 60}) 9 | |> ellipse({30, 40}, fill: :green, stroke: {4, :gray}, t: {110, 60}) 10 | |> text("Hello", t: {15, 150}, font: :roboto, font_size: 50) 11 | |> text("World", t: {15, 190}, fill: :purple, font: :roboto_mono, font_size: 30) 12 | |> analog_clock(seconds: true, radius: 80, ticks: true, t: {230, 100}) 13 | |> text("", id: :event, t: {0, 220}) 14 | 15 | @impl Scenic.Scene 16 | def init(scene, _param, _opts) do 17 | scene = push_graph(scene, @graph) 18 | :ok = request_input(scene, [:cursor_pos, :cursor_scroll, :cursor_button]) 19 | {:ok, scene} 20 | end 21 | 22 | @impl Scenic.Scene 23 | def handle_input(event, _context, scene) do 24 | graph = 25 | @graph 26 | |> Graph.modify(:event, &text(&1, "#{inspect(event)}")) 27 | 28 | scene = push_graph(scene, graph) 29 | {:noreply, scene} 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /hello_scenic/lib/scenes/readme.txt: -------------------------------------------------------------------------------- 1 | scene_readme.txt -------------------------------------------------------------------------------- /hello_scenic/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Add custom options here 2 | 3 | ## Distributed Erlang Options 4 | ## The cookie needs to be configured prior to vm boot for 5 | ## for read only filesystem. 6 | 7 | -setcookie <%= @release.options[:cookie] %> 8 | 9 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 10 | ## break handler and possibly exiting the VM. 11 | +Bc 12 | 13 | # Allow time warps so that the Erlang system time can more closely match the 14 | # OS system time. 15 | +C multi_time_warp 16 | 17 | ## Load code at system startup 18 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 19 | -mode embedded 20 | 21 | # Load code as per the boot script since not using archives 22 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 23 | -code_path_choice strict 24 | 25 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 26 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 27 | +sbwt none 28 | +sbwtdcpu none 29 | +sbwtdio none 30 | 31 | ## Save the shell history between reboots 32 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 33 | -kernel shell_history enabled 34 | 35 | ## Enable heartbeat monitoring of the Erlang runtime system 36 | -heart -env HEART_BEAT_TIMEOUT 30 37 | 38 | ## Start the Elixir shell 39 | 40 | -noshell 41 | -user elixir 42 | -run elixir start_cli 43 | 44 | ## Enable colors in the shell 45 | -elixir ansi_enabled true 46 | 47 | ## Options added after -extra are interpreted as plain arguments and can be 48 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 49 | ## interpreted by Elixir and anything afterwards is left around for other IEx 50 | ## and user applications. 51 | -extra --no-halt 52 | -- 53 | --dot-iex /etc/iex.exs 54 | -------------------------------------------------------------------------------- /hello_scenic/rootfs_overlay/etc/iex.exs: -------------------------------------------------------------------------------- 1 | NervesMOTD.print() 2 | 3 | # Add Toolshed helpers to the IEx session 4 | use Toolshed 5 | -------------------------------------------------------------------------------- /hello_snmp_agent/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: [ 4 | "{mix,.formatter}.exs", 5 | "{config,lib,test}/**/*.{ex,exs}", 6 | "rootfs_overlay/etc/iex.exs" 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /hello_snmp_agent/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # SNMP generated files are placed here 20 | /priv/ 21 | -------------------------------------------------------------------------------- /hello_snmp_agent/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | config :hello_snmp_agent, target: Mix.target() 12 | 13 | # Customize non-Elixir parts of the firmware. See 14 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 15 | 16 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 17 | 18 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 19 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 20 | 21 | config :nerves, source_date_epoch: "1634820789" 22 | 23 | # Use Ringlogger as the logger backend and remove :console. 24 | # See https://hexdocs.pm/ring_logger/readme.html for more information on 25 | # configuring ring_logger. 26 | 27 | config :logger, backends: [RingLogger] 28 | 29 | config :hello_snmp_agent, HelloSnmpAgent.Agent, 30 | dir: './priv/', 31 | versions: [:v1, :v2, :v3], 32 | port: "SNMP_PORT" |> System.get_env("161") |> String.to_integer(), 33 | transports: ["127.0.0.1"], 34 | security: [ 35 | [user: "public", password: "password", access: :public], 36 | [user: "admin", password: "adminpassword", access: [:public, :secure]] 37 | ] 38 | 39 | if Mix.target() == :host do 40 | import_config "host.exs" 41 | else 42 | import_config "target.exs" 43 | end 44 | -------------------------------------------------------------------------------- /hello_snmp_agent/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | -------------------------------------------------------------------------------- /hello_snmp_agent/lib/hello_snmp_agent.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSnmpAgent do 2 | @moduledoc """ 3 | The HelloSnmpAgent example 4 | """ 5 | end 6 | -------------------------------------------------------------------------------- /hello_snmp_agent/lib/hello_snmp_agent/application.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSnmpAgent.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | # See https://hexdocs.pm/elixir/Supervisor.html 11 | # for other strategies and supported options 12 | opts = [strategy: :one_for_one, name: HelloSnmpAgent.Supervisor] 13 | 14 | children = 15 | [ 16 | {HelloSnmpAgent.Agent, %{}} 17 | # Children for all targets 18 | # Starts a worker by calling: HelloSnmpAgent.Worker.start_link(arg) 19 | # {HelloSnmpAgent.Worker, arg}, 20 | ] ++ children(target()) 21 | 22 | Supervisor.start_link(children, opts) 23 | end 24 | 25 | # List all child processes to be supervised 26 | def children(:host) do 27 | [ 28 | # Children that only run on the host 29 | # Starts a worker by calling: HelloSnmpAgent.Worker.start_link(arg) 30 | # {HelloSnmpAgent.Worker, arg}, 31 | ] 32 | end 33 | 34 | def children(_target) do 35 | [ 36 | # Children for all targets except host 37 | # Starts a worker by calling: HelloSnmpAgent.Worker.start_link(arg) 38 | # {HelloSnmpAgent.Worker, arg}, 39 | ] 40 | end 41 | 42 | def target() do 43 | Application.get_env(:hello_snmp_agent, :target) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /hello_snmp_agent/lib/hello_snmp_agent/snmp/agent.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSnmpAgent.Agent do 2 | @moduledoc """ 3 | Example Agent which is used by the elixir-snmp dependency to generate some 4 | SNMP-related files. The numbers at the end of the example snmp commands listed 5 | below are called 'oids', they must correspond to what is in your 6 | hello_snmp_agent/mibs/*.mib and what you list below via the mib macro. 7 | 8 | Example commands: 9 | on host: 10 | - snmpwalk -c public localhost .1.3.6.1.3.17 11 | - snmpget -c public localhost .1.3.6.1.3.17.1.0 12 | - snmpget -c public localhost .1.3.6.1.3.17.2.0 13 | - snmpset -c public localhost .1.3.6.1.3.17.1.0 i 0 14 | 15 | on target: 16 | - snmpwalk -c public nerves.local .1.3.6.1.3.17 17 | - snmpget -c public nerves.local .1.3.6.1.3.17.1.0 18 | - snmpget -c public nerves.local .1.3.6.1.3.17.2.0 19 | - snmpset -c public nerves.local .1.3.6.1.3.17.1.0 i 0 20 | """ 21 | use Snmp.Agent.Handler, otp_app: :hello_snmp_agent 22 | 23 | # Mandatory MIBs 24 | mib(Standard) 25 | mib(Framework) 26 | 27 | # Application MIBs 28 | mib(HelloSnmpAgent.Mib.MyMib) 29 | 30 | # VACM model 31 | view :public do 32 | # Experimental 33 | include([1, 3, 6, 1, 3]) 34 | end 35 | 36 | view :private do 37 | include([1, 3, 6]) 38 | end 39 | 40 | # Typically you would not set everything to public, this is here for easy 41 | # demonstration purposes (easy to issue snmpwalk/snmpget/snmpset commands) 42 | access(:public, 43 | versions: [:v1, :v2c, :usm], 44 | level: :noAuthNoPriv, 45 | read_view: :public, 46 | write_view: :public, 47 | notify_view: :public 48 | ) 49 | 50 | access(:secure, 51 | versions: [:usm], 52 | level: :authPriv, 53 | read_view: :private, 54 | write_view: :private, 55 | notify_view: :private 56 | ) 57 | end 58 | -------------------------------------------------------------------------------- /hello_snmp_agent/lib/hello_snmp_agent/snmp/mib/framework.ex: -------------------------------------------------------------------------------- 1 | defmodule Framework do 2 | use Snmp.Mib.Framework, 3 | otp_app: :hello_snmp_agent, 4 | conf: [ 5 | {:snmpEngineID, [1]}, 6 | {:snmpEngineMaxMessageSize, 1500} 7 | ] 8 | end 9 | -------------------------------------------------------------------------------- /hello_snmp_agent/lib/hello_snmp_agent/snmp/mib/my_mib.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSnmpAgent.Mib.MyMib do 2 | @moduledoc """ 3 | This is the where the 'instrumentation' functions go that match what you have 4 | in hello_snmp_agent/mibs/MY-MIB.mib. 5 | """ 6 | 7 | use Snmp.Mib, name: "MY-MIB" 8 | 9 | alias Circuits.GPIO 10 | 11 | def someObjectIFOutput(:get) do 12 | {:ok, gpio} = GPIO.open(26, :output) 13 | value = GPIO.read(gpio) 14 | 15 | {:value, value} 16 | end 17 | 18 | @doc """ 19 | Naturally, you'd usually follow the pattern of having a GenServer to interact 20 | with GPIO. This is here just to demonstrate how to easily interact via SNMP. 21 | """ 22 | def someObjectIFOutput(:set, val) do 23 | {:ok, gpio} = GPIO.open(26, :output) 24 | GPIO.write(gpio, val) 25 | 26 | :noError 27 | end 28 | 29 | def someObjectIFLevel(:get), do: {:value, 123} 30 | def someObjectIFLevel(:set, _val), do: :noError 31 | end 32 | -------------------------------------------------------------------------------- /hello_snmp_agent/lib/hello_snmp_agent/snmp/mib/standard.ex: -------------------------------------------------------------------------------- 1 | defmodule Standard do 2 | use Snmp.Mib.Standard, 3 | otp_app: :hello_snmp_agent, 4 | conf: [ 5 | {:sysObjectID, [1, 2, 3]}, 6 | {:sysServices, 72}, 7 | {:snmpEnableAuthenTraps, :disabled} 8 | ] 9 | end 10 | -------------------------------------------------------------------------------- /hello_snmp_agent/mibs/MY-MIB.mib: -------------------------------------------------------------------------------- 1 | MY-MIB DEFINITIONS ::= BEGIN 2 | 3 | IMPORTS 4 | experimental FROM RFC1155-SMI 5 | OBJECT-TYPE FROM RFC-1212 6 | ; 7 | 8 | someObject OBJECT IDENTIFIER ::= { experimental 17 } 9 | 10 | someObjectIFOutput OBJECT-TYPE 11 | SYNTAX INTEGER 12 | { 13 | off(0), 14 | on(1) 15 | } 16 | ACCESS read-write 17 | STATUS mandatory 18 | DESCRIPTION "IF Output, where: 19 | 0) 'Off' 20 | 1) 'On' 21 | " 22 | ::= { someObject 1 } 23 | 24 | someObjectIFLevel OBJECT-TYPE 25 | SYNTAX INTEGER 26 | ACCESS read-write 27 | STATUS mandatory 28 | DESCRIPTION "IF Level in 0.01 dBm increments 29 | " 30 | ::= { someObject 2 } 31 | 32 | 33 | END 34 | -------------------------------------------------------------------------------- /hello_snmp_agent/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Customize flags given to the VM: http://erlang.org/doc/man/erl.html 2 | 3 | ## Do not set -name or -sname here. Prefer configuring them at runtime 4 | ## Configure -setcookie in the mix.exs release section or at runtime 5 | 6 | ## Number of dirty schedulers doing IO work (file, sockets, and others) 7 | ##+SDio 5 8 | 9 | ## Increase number of concurrent ports/sockets 10 | ##+Q 65536 11 | 12 | ## Tweak GC to run more often 13 | ##-env ERL_FULLSWEEP_AFTER 10 14 | 15 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 16 | ## break handler and possibly exiting the VM. 17 | +Bc 18 | 19 | # Allow time warps so that the Erlang system time can more closely match the 20 | # OS system time. 21 | +C multi_time_warp 22 | 23 | ## Load code at system startup 24 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 25 | -mode embedded 26 | 27 | # Load code as per the boot script since not using archives 28 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 29 | -code_path_choice strict 30 | 31 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 32 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 33 | +sbwt none 34 | +sbwtdcpu none 35 | +sbwtdio none 36 | 37 | ## Save the shell history between reboots 38 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 39 | -kernel shell_history enabled 40 | 41 | ## Enable heartbeat monitoring of the Erlang runtime system 42 | -heart -env HEART_BEAT_TIMEOUT 30 43 | 44 | ## Start the Elixir shell 45 | 46 | -noshell 47 | -user elixir 48 | -run elixir start_cli 49 | 50 | ## Enable colors in the shell 51 | -elixir ansi_enabled true 52 | 53 | ## Options added after -extra are interpreted as plain arguments and can be 54 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 55 | ## interpreted by Elixir and anything afterwards is left around for other IEx 56 | ## and user applications. 57 | -extra --no-halt 58 | -- 59 | --dot-iex /etc/iex.exs 60 | -------------------------------------------------------------------------------- /hello_snmp_agent/rootfs_overlay/etc/iex.exs: -------------------------------------------------------------------------------- 1 | NervesMOTD.print() 2 | 3 | # Add Toolshed helpers to the IEx session 4 | use Toolshed 5 | -------------------------------------------------------------------------------- /hello_snmp_manager/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: [ 4 | "{mix,.formatter}.exs", 5 | "{config,lib,test}/**/*.{ex,exs}", 6 | "rootfs_overlay/etc/iex.exs" 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /hello_snmp_manager/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Temporary files, for example, from tests. 20 | /tmp/ 21 | -------------------------------------------------------------------------------- /hello_snmp_manager/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | config :hello_snmp_manager, target: Mix.target() 12 | 13 | # Customize non-Elixir parts of the firmware. See 14 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 15 | 16 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 17 | 18 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 19 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 20 | 21 | config :nerves, source_date_epoch: "1619044926" 22 | 23 | if Mix.target() == :host do 24 | import_config "host.exs" 25 | else 26 | import_config "target.exs" 27 | end 28 | -------------------------------------------------------------------------------- /hello_snmp_manager/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | 5 | # SNMP Manager with relatives paths 6 | config :snmp, :manager, config: [dir: ~c"./rootfs_overlay/snmp", db_dir: ~c"./tmp"] 7 | -------------------------------------------------------------------------------- /hello_snmp_manager/lib/hello_snmp_manager.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSnmpManager do 2 | @moduledoc """ 3 | Simple example to poll a remote network element via SNMP. This 4 | demonstrates using a single request to obtain two OIDs from the 5 | remote system. 6 | 7 | The remote host to poll (the "agent") is specified in the SNMP 8 | `agent.conf` configuration file. See the README.md for details 9 | on this file's settings. 10 | 11 | See README.md for build instructions. 12 | """ 13 | use GenServer 14 | 15 | require Logger 16 | 17 | # Durations are in milliseconds 18 | @polling_interval 10_000 19 | 20 | @spec start_link(keyword()) :: GenServer.on_start() 21 | def start_link(init_args) do 22 | GenServer.start_link(__MODULE__, init_args) 23 | end 24 | 25 | @impl GenServer 26 | def init(_init_args) do 27 | Logger.debug("Starting SNMP Poller") 28 | _ = :timer.send_interval(@polling_interval, :tick) 29 | {:ok, :no_state} 30 | end 31 | 32 | @impl GenServer 33 | def handle_info(:tick, state) do 34 | Logger.debug("Sending SNMP Request for System Information") 35 | 36 | :snmpm.sync_get2(~c"default_user", ~c"default_agent", [ 37 | # OID for sysDescr 38 | [1, 3, 6, 1, 2, 1, 1, 1, 0], 39 | # OID for sysName 40 | [1, 3, 6, 1, 2, 1, 1, 5, 0] 41 | ]) 42 | |> parse_response() 43 | 44 | {:noreply, state} 45 | end 46 | 47 | defp parse_response({:ok, _, _} = snmp_response) do 48 | {_status, 49 | {_, _, 50 | [ 51 | {_, _, _, system_description, _}, 52 | {_, _, _, system_name, _} 53 | ]}, _} = snmp_response 54 | 55 | Logger.debug("SNMP Response Successful") 56 | Logger.debug("System Name: #{system_name}") 57 | Logger.debug("System Description: #{system_description}") 58 | end 59 | 60 | defp parse_response({:error, _, _} = _snmp_response) do 61 | Logger.debug("SNMP Response Failed") 62 | end 63 | 64 | defp parse_response(_snmp_response) do 65 | Logger.debug("SNMP Response Failed") 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /hello_snmp_manager/lib/hello_snmp_manager/application.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSnmpManager.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | def start(_type, _args) do 9 | # See https://hexdocs.pm/elixir/Supervisor.html 10 | # for other strategies and supported options 11 | opts = [strategy: :one_for_one, name: HelloSnmpManager.Supervisor] 12 | 13 | children = [ 14 | {HelloSnmpManager, []} 15 | ] 16 | 17 | Supervisor.start_link(children, opts) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /hello_snmp_manager/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Add custom options here 2 | 3 | ## Distributed Erlang Options 4 | ## The cookie needs to be configured prior to vm boot for 5 | ## for read only filesystem. 6 | 7 | -setcookie <%= @release.options[:cookie] %> 8 | 9 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 10 | ## break handler and possibly exiting the VM. 11 | +Bc 12 | 13 | # Allow time warps so that the Erlang system time can more closely match the 14 | # OS system time. 15 | +C multi_time_warp 16 | 17 | ## Load code at system startup 18 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 19 | -mode embedded 20 | 21 | # Load code as per the boot script since not using archives 22 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 23 | -code_path_choice strict 24 | 25 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 26 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 27 | +sbwt none 28 | +sbwtdcpu none 29 | +sbwtdio none 30 | 31 | ## Save the shell history between reboots 32 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 33 | -kernel shell_history enabled 34 | 35 | ## Enable heartbeat monitoring of the Erlang runtime system 36 | -heart -env HEART_BEAT_TIMEOUT 30 37 | 38 | ## Start the Elixir shell 39 | 40 | -noshell 41 | -user elixir 42 | -run elixir start_cli 43 | 44 | ## Enable colors in the shell 45 | -elixir ansi_enabled true 46 | 47 | ## Options added after -extra are interpreted as plain arguments and can be 48 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 49 | ## interpreted by Elixir and anything afterwards is left around for other IEx 50 | ## and user applications. 51 | -extra --no-halt 52 | -- 53 | --dot-iex /etc/iex.exs 54 | -------------------------------------------------------------------------------- /hello_snmp_manager/rootfs_overlay/etc/iex.exs: -------------------------------------------------------------------------------- 1 | NervesMOTD.print() 2 | 3 | # Add Toolshed helpers to the IEx session 4 | use Toolshed 5 | -------------------------------------------------------------------------------- /hello_snmp_manager/rootfs_overlay/snmp/agents.conf: -------------------------------------------------------------------------------- 1 | {"default_user", "default_agent", "public", [192, 168, 1, 1], 161, "default", 2 | infinity, 1000, v2, v2c, "initial", noAuthNoPriv}. 3 | -------------------------------------------------------------------------------- /hello_snmp_manager/rootfs_overlay/snmp/manager.conf: -------------------------------------------------------------------------------- 1 | {address, [127,0,0,1]}. 2 | {port, 5000}. 3 | {engine_id, "default"}. 4 | {max_message_size, 1000}. -------------------------------------------------------------------------------- /hello_snmp_manager/rootfs_overlay/snmp/users.conf: -------------------------------------------------------------------------------- 1 | {"default_user", snmpm_user_default, undefined, []}. -------------------------------------------------------------------------------- /hello_sqlite/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | import_deps: [:ecto], 4 | inputs: [ 5 | "*.{ex,exs}", 6 | "priv/*/seeds.exs", 7 | "{config,lib,test}/**/*.{ex,exs}", 8 | "rootfs_overlay/etc/iex.exs" 9 | ], 10 | subdirectories: ["priv/*/migrations"] 11 | ] 12 | -------------------------------------------------------------------------------- /hello_sqlite/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | -------------------------------------------------------------------------------- /hello_sqlite/README.md: -------------------------------------------------------------------------------- 1 | # HelloSqlite 2 | 3 | This example demonstrates a basic project for using the 4 | [Ecto SQLite Adapter](https://github.com/elixir-sqlite/ecto_sqlite3). 5 | 6 | See [scheduler_usage_poller.ex](./lib/hello_sqlite/scheduler_usage_poller.ex) for an example of Sqlite being used. 7 | 8 | ## Hardware 9 | 10 | The example below assumes a Raspberry Pi 0 connected over the USB. Other 11 | official and many unofficial Nerves targets work as well. 12 | 13 | ## How to use this repository 14 | 15 | 0. Go to the `hello_sqlite` directory 16 | 17 | 1. Set up your build environment 18 | 19 | ```shell 20 | # Specify the target hardware. See the mix.exs for options 21 | export MIX_TARGET=rpi0 22 | 23 | # To hard-code WiFi credentials in the firmware, you can set 24 | # the SSID and optionally a password here 25 | export WIFI_SSID=your_wifi_name 26 | export WIFI_PASSPHRASE=your_wifi_password 27 | ``` 28 | 29 | 2. Get dependencies, build firmware, and burn it to an SD card 30 | 31 | ```shell 32 | mix deps.get 33 | mix firmware 34 | mix firmware.burn 35 | ``` 36 | 37 | 3. Insert the SD card into your target board and power up 38 | 39 | 4. Wait to finish booting. 40 | 41 | 5. SSH into the board: `ssh nerves.local` 42 | 43 | 6. from the IEx prompt 44 | 45 | ```elixir 46 | # Get 5 entries from the SchedulerUsage table and print them out 47 | HelloSqlite.print_recent(5) 48 | +------------------------------------------------------+ 49 | | Scheduler Usage | 50 | +---------------------+---------+----------------------+ 51 | | Timestamp | Percent | Util | 52 | +---------------------+---------+----------------------+ 53 | | 2021-04-07 14:53:27 | 0.0 | 4.61004300282592e-5 | 54 | | 2021-04-07 14:52:56 | 0.0 | 1.968093695606747e-5 | 55 | | 2021-04-07 14:52:25 | 0.0 | 8.095389473073943e-5 | 56 | | 2021-04-07 14:51:54 | 0.0 | 9.218367384342268e-6 | 57 | +---------------------+---------+----------------------+ 58 | :ok 59 | ``` 60 | 61 | ## Learn More 62 | 63 | - Official docs: https://hexdocs.pm/nerves/getting-started.html 64 | - Official website: https://nerves-project.org/ 65 | - Source: https://github.com/nerves-project/nerves 66 | 67 | - `ecto_sqlite3` docs: https://github.com/elixir-sqlite/ecto_sqlite3 68 | - Ecto Docs: https://hexdocs.pm/ecto/ 69 | -------------------------------------------------------------------------------- /hello_sqlite/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | config :hello_sqlite, target: Mix.target() 12 | 13 | # Customize non-Elixir parts of the firmware. See 14 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 15 | 16 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 17 | 18 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 19 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 20 | 21 | config :nerves, source_date_epoch: "1616101946" 22 | 23 | # Use Ringlogger as the logger backend and remove :console. 24 | # See https://hexdocs.pm/ring_logger/readme.html for more information on 25 | # configuring ring_logger. 26 | 27 | config :logger, backends: [RingLogger] 28 | 29 | if Mix.target() == :host do 30 | import_config "host.exs" 31 | else 32 | import_config "target.exs" 33 | end 34 | -------------------------------------------------------------------------------- /hello_sqlite/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | 5 | config :hello_sqlite, 6 | ecto_repos: [HelloSqlite.Repo] 7 | 8 | config :hello_sqlite, HelloSqlite.Repo, 9 | database: "/tmp/hello_sqlite.db", 10 | show_sensitive_data_on_connection_error: false, 11 | journal_mode: :wal, 12 | cache_size: -64000, 13 | temp_store: :memory, 14 | pool_size: 1 15 | -------------------------------------------------------------------------------- /hello_sqlite/lib/hello_sqlite.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSqlite do 2 | @moduledoc """ 3 | Sqlite demo application 4 | """ 5 | 6 | @spec save_usage() :: :ok 7 | defdelegate save_usage(), to: HelloSqlite.SchedulerUsagePoller 8 | 9 | @spec print_recent(non_neg_integer()) :: :ok 10 | defdelegate print_recent(count \\ 5), to: HelloSqlite.SchedulerUsagePoller 11 | end 12 | -------------------------------------------------------------------------------- /hello_sqlite/lib/hello_sqlite/application.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSqlite.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | def start(_type, _args) do 9 | # See https://hexdocs.pm/elixir/Supervisor.html 10 | # for other strategies and supported options 11 | opts = [strategy: :one_for_one, name: HelloSqlite.Supervisor] 12 | 13 | children = 14 | [ 15 | # Children for all targets 16 | HelloSqlite.Repo, 17 | {Task, &HelloSqlite.MigrationHelpers.migrate/0}, 18 | HelloSqlite.SchedulerUsagePoller 19 | ] ++ children(target()) 20 | 21 | Supervisor.start_link(children, opts) 22 | end 23 | 24 | # List all child processes to be supervised 25 | def children(:host) do 26 | [ 27 | # Children that only run on the host 28 | ] 29 | end 30 | 31 | def children(_target) do 32 | [ 33 | # Children for all targets except host 34 | ] 35 | end 36 | 37 | def target() do 38 | Application.get_env(:hello_sqlite, :target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /hello_sqlite/lib/hello_sqlite/migration_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSqlite.MigrationHelpers do 2 | @app :hello_sqlite 3 | 4 | def migrate() do 5 | for repo <- repos() do 6 | {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) 7 | end 8 | end 9 | 10 | def rollback(repo, version) do 11 | {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) 12 | end 13 | 14 | defp repos() do 15 | _ = Application.load(@app) 16 | Application.fetch_env!(@app, :ecto_repos) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /hello_sqlite/lib/hello_sqlite/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSqlite.Repo do 2 | use Ecto.Repo, otp_app: :hello_sqlite, adapter: Ecto.Adapters.SQLite3 3 | end 4 | -------------------------------------------------------------------------------- /hello_sqlite/lib/hello_sqlite/scheduler_usage.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSqlite.SchedulerUsage do 2 | use Ecto.Schema 3 | 4 | schema "scheduler_usage" do 5 | field :total_util, :float 6 | field :total_percent, :float 7 | timestamps() 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /hello_sqlite/lib/hello_sqlite/scheduler_usage_poller.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloSqlite.SchedulerUsagePoller do 2 | @moduledoc """ 3 | Saves scheduler utilization to Sqlite every 30 seconds or whenever 4 | `save_usage/1` is called. 5 | """ 6 | 7 | use GenServer 8 | 9 | alias HelloSqlite.{Repo, SchedulerUsage} 10 | import Ecto.Query 11 | 12 | @timeout 30_000 13 | 14 | @doc false 15 | @spec start_link(keyword()) :: GenServer.on_start() 16 | def start_link(init_args) do 17 | GenServer.start_link(__MODULE__, init_args, name: __MODULE__) 18 | end 19 | 20 | @doc """ 21 | Pretty print entries from the Scheduler Usage table 22 | """ 23 | @spec print_recent(non_neg_integer()) :: :ok 24 | def print_recent(entries) do 25 | rows = 26 | Repo.all( 27 | from su in SchedulerUsage, 28 | order_by: [desc: su.id], 29 | limit: ^entries, 30 | select: [su.inserted_at, su.total_percent, su.total_util] 31 | ) 32 | 33 | header = ["Timestamp", "Percent", "Util"] 34 | title = "Scheduler Usage" 35 | 36 | TableRex.quick_render!(rows, header, title) 37 | |> IO.puts() 38 | end 39 | 40 | @doc """ 41 | Manually trigger a usage record to be saved 42 | """ 43 | @spec save_usage() :: :ok 44 | def save_usage() do 45 | GenServer.call(__MODULE__, :save_usage) 46 | end 47 | 48 | @impl GenServer 49 | def init(_init_args) do 50 | {:ok, _tref} = :timer.send_interval(@timeout, :tick) 51 | 52 | # Save something so there's always at least one row 53 | # in the table for the demo. 54 | do_save_usage() 55 | 56 | {:ok, nil} 57 | end 58 | 59 | @impl GenServer 60 | def handle_call(:save_usage, _from, state) do 61 | do_save_usage() 62 | 63 | {:reply, :ok, state} 64 | end 65 | 66 | @impl GenServer 67 | def handle_info(:tick, state) do 68 | do_save_usage() 69 | 70 | {:noreply, state} 71 | end 72 | 73 | defp do_save_usage() do 74 | util = :scheduler.utilization(1) 75 | {:total, total_util, total_percent_str} = :lists.keyfind(:total, 1, util) 76 | {total_percent, "%"} = Float.parse(to_string(total_percent_str)) 77 | 78 | %SchedulerUsage{ 79 | total_percent: total_percent, 80 | total_util: total_util 81 | } 82 | |> Repo.insert!() 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /hello_sqlite/priv/repo/migrations/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto_sql], 3 | inputs: ["*.exs"] 4 | ] 5 | -------------------------------------------------------------------------------- /hello_sqlite/priv/repo/migrations/20210318212322_add_scheduler_usage.exs: -------------------------------------------------------------------------------- 1 | defmodule HelloSqlite.Repo.Migrations.AddSchedulerUsage do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:scheduler_usage) do 6 | add :total_util, :float 7 | add :total_percent, :float 8 | timestamps() 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /hello_sqlite/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Add custom options here 2 | 3 | ## Distributed Erlang Options 4 | ## The cookie needs to be configured prior to vm boot for 5 | ## for read only filesystem. 6 | 7 | -setcookie <%= @release.options[:cookie] %> 8 | 9 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 10 | ## break handler and possibly exiting the VM. 11 | +Bc 12 | 13 | # Allow time warps so that the Erlang system time can more closely match the 14 | # OS system time. 15 | +C multi_time_warp 16 | 17 | ## Load code at system startup 18 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 19 | -mode embedded 20 | 21 | # Load code as per the boot script since not using archives 22 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 23 | -code_path_choice strict 24 | 25 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 26 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 27 | +sbwt none 28 | +sbwtdcpu none 29 | +sbwtdio none 30 | 31 | ## Save the shell history between reboots 32 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 33 | -kernel shell_history enabled 34 | 35 | ## Enable heartbeat monitoring of the Erlang runtime system 36 | -heart -env HEART_BEAT_TIMEOUT 30 37 | 38 | ## Start the Elixir shell 39 | 40 | -noshell 41 | -user elixir 42 | -run elixir start_cli 43 | 44 | ## Enable colors in the shell 45 | -elixir ansi_enabled true 46 | 47 | ## Options added after -extra are interpreted as plain arguments and can be 48 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 49 | ## interpreted by Elixir and anything afterwards is left around for other IEx 50 | ## and user applications. 51 | -extra --no-halt 52 | -- 53 | --dot-iex /etc/iex.exs 54 | -------------------------------------------------------------------------------- /hello_sqlite/rootfs_overlay/etc/iex.exs: -------------------------------------------------------------------------------- 1 | NervesMOTD.print() 2 | 3 | # Add Toolshed helpers to the IEx session 4 | use Toolshed 5 | -------------------------------------------------------------------------------- /hello_wifi/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: [ 4 | "{mix,.formatter}.exs", 5 | "{config,lib,test}/**/*.{ex,exs}", 6 | "rootfs_overlay/etc/iex.exs" 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /hello_wifi/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | -------------------------------------------------------------------------------- /hello_wifi/README.md: -------------------------------------------------------------------------------- 1 | # Hello WiFi 2 | 3 | This example builds a Nerves firmware image for supported Nerves devices that 4 | demonstrates using [`VintageNetWizard`](https://hexdocs.pm/vintage_net_wizard) 5 | WiFi configuration utility. The wizard will run on boot and also after a button 6 | has been held down for 5 seconds. 7 | 8 | If you don't have a button, you can use a jumper wire to temporarily connect 9 | 3.3V power directly to the pin (the default is GPIO 17 which is pin 11 on the 10 | Raspberry Pi's GPIO header). 11 | 12 | [![Raspberry Pi Pinout from pinout.xyz](assets/pinout-xyz.png)](https://pinout.xyz/#) 13 | 14 | If you have a Raspberry Pi hat with a button connected to a different GPIO pin, 15 | you can specify which pin to use in your config: 16 | 17 | ```elixir 18 | config :hello_wifi, button_pin: 27 19 | ``` 20 | 21 | If you're not using a Raspberry Pi, then modify the pin number to a GPIO that 22 | makes sense on your board. 23 | 24 | The next step is to build the firmware. Make sure that you've installed Nerves 25 | and run the following: 26 | 27 | ```sh 28 | cd hello_wifi 29 | 30 | # Set the target to rpi0, rpi3, or rpi4 depending on what you have 31 | export MIX_TARGET=rpi3 32 | mix deps.get 33 | mix firmware 34 | 35 | # Insert a MicroSD card or whatever media your board takes 36 | mix burn 37 | ``` 38 | 39 | Place the MicroSD card in the Raspberry Pi and power it on. You should see a 40 | WiFi access point appear with the SSID "*hello_wifi*" that you should connect 41 | to. Once connected, a captive portal dialog screen should come up with the 42 | configuration page. If it doesn't for some reason, you can also get to the page 43 | manually from the browser at either of these addresses: 44 | 45 | * [http://hello_wifi.config](http://hello_wifi.config/) 46 | * [http://192.168.0.1/](http://192.168.0.1/) 47 | 48 | Note: The SSID and DNS name can be changed in `config/target.exs` 49 | 50 | ```elixir 51 | config :vintage_net_wizard, 52 | ssid: "my_ssid", 53 | dns_name: "some.site" 54 | ``` 55 | -------------------------------------------------------------------------------- /hello_wifi/assets/pinout-xyz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_wifi/assets/pinout-xyz.png -------------------------------------------------------------------------------- /hello_wifi/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | config :hello_wifi, target: Mix.target() 12 | 13 | # Customize non-Elixir parts of the firmware. See 14 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 15 | 16 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 17 | 18 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 19 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 20 | 21 | config :nerves, source_date_epoch: "1577975236" 22 | 23 | # Use Ringlogger as the logger backend and remove :console. 24 | # See https://hexdocs.pm/ring_logger/readme.html for more information on 25 | # configuring ring_logger. 26 | 27 | config :logger, backends: [RingLogger] 28 | 29 | if Mix.target() == :host do 30 | import_config "host.exs" 31 | else 32 | import_config "target.exs" 33 | end 34 | -------------------------------------------------------------------------------- /hello_wifi/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | -------------------------------------------------------------------------------- /hello_wifi/config/target.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Use shoehorn to start the main application. See the shoehorn 4 | # docs for separating out critical OTP applications such as those 5 | # involved with firmware updates. 6 | 7 | config :shoehorn, init: [:nerves_runtime, :nerves_pack] 8 | 9 | # Erlinit can be configured without a rootfs_overlay. See 10 | # https://github.com/nerves-project/erlinit/ for more information on 11 | # configuring erlinit. 12 | 13 | config :nerves, :erlinit, update_clock: true 14 | 15 | # Configure the device for SSH IEx prompt access and firmware updates 16 | # 17 | # * See https://hexdocs.pm/nerves_ssh/readme.html for general SSH configuration 18 | # * See https://hexdocs.pm/ssh_subsystem_fwup/readme.html for firmware updates 19 | 20 | keys = 21 | [ 22 | Path.join([System.user_home!(), ".ssh", "id_rsa.pub"]), 23 | Path.join([System.user_home!(), ".ssh", "id_ecdsa.pub"]), 24 | Path.join([System.user_home!(), ".ssh", "id_ed25519.pub"]) 25 | ] 26 | |> Enum.filter(&File.exists?/1) 27 | 28 | if keys == [], 29 | do: 30 | Mix.raise(""" 31 | No SSH public keys found in ~/.ssh. An ssh authorized key is needed to 32 | log into the Nerves device and update firmware on it using ssh. 33 | See your project's config.exs for this error message. 34 | """) 35 | 36 | config :nerves_ssh, 37 | authorized_keys: Enum.map(keys, &File.read!/1) 38 | 39 | # Set the SSID for the network to join and the DNS name to use 40 | # in the browser. 41 | # see https://github.com/nerves-networking/vintage_net_wizard 42 | config :vintage_net_wizard, 43 | ssid: "hello_wifi", 44 | dns_name: "hello_wifi.config" 45 | 46 | # Import target specific config. This must remain at the bottom 47 | # of this file so it overrides the configuration defined above. 48 | # Uncomment to use target specific configurations 49 | 50 | # import_config "#{Mix.target()}.exs" 51 | -------------------------------------------------------------------------------- /hello_wifi/lib/button.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloWiFi.Button do 2 | use GenServer 3 | 4 | @moduledoc """ 5 | This GenServer starts the wizard if a button is depressed for long enough. 6 | """ 7 | 8 | alias Circuits.GPIO 9 | 10 | @doc """ 11 | Start the button monitor 12 | 13 | Pass an index to the GPIO that's connected to the button. 14 | """ 15 | @spec start_link(non_neg_integer()) :: GenServer.on_start() 16 | def start_link(gpio_pin) do 17 | GenServer.start_link(__MODULE__, gpio_pin) 18 | end 19 | 20 | @impl true 21 | def init(gpio_pin) do 22 | {:ok, gpio} = GPIO.open(gpio_pin, :input) 23 | :ok = GPIO.set_interrupts(gpio, :both) 24 | {:ok, %{pin: gpio_pin, gpio: gpio}} 25 | end 26 | 27 | @impl true 28 | def handle_info({:circuits_gpio, gpio_pin, _timestamp, 1}, %{pin: gpio_pin} = state) do 29 | # Button pressed. Start a timer to launch the wizard when it's long enough 30 | {:noreply, state, 5_000} 31 | end 32 | 33 | @impl true 34 | def handle_info({:circuits_gpio, gpio_pin, _timestamp, 0}, %{pin: gpio_pin} = state) do 35 | # Button released. The GenServer timer is implicitly cancelled by receiving this message. 36 | {:noreply, state} 37 | end 38 | 39 | @impl true 40 | def handle_info(:timeout, state) do 41 | :ok = VintageNetWizard.run_wizard(on_exit: {HelloWiFi, :on_wizard_exit, []}) 42 | {:noreply, state} 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /hello_wifi/lib/hello_wifi.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloWiFi do 2 | use Application 3 | 4 | require Logger 5 | 6 | @ifname "wlan0" 7 | 8 | @spec start(Application.start_type(), any()) :: {:error, any} | {:ok, pid()} 9 | def start(_type, _args) do 10 | maybe_start_wifi_wizard() 11 | 12 | gpio_pin = Application.get_env(:hello_wifi, :button_pin, 17) 13 | 14 | children = [ 15 | {HelloWiFi.Button, gpio_pin} 16 | ] 17 | 18 | Supervisor.start_link(children, strategy: :one_for_one, name: HelloWiFi.Supervisor) 19 | end 20 | 21 | @doc false 22 | def on_wizard_exit() do 23 | # This function is used as a callback when the WiFi Wizard 24 | # exits which is useful if you need to do work after 25 | # configuration is done, like restart web servers that might 26 | # share a port with the wizard, etc etc 27 | Logger.info("[HelloWiFi] - WiFi Wizard stopped") 28 | end 29 | 30 | defp maybe_start_wifi_wizard() do 31 | with true <- has_wifi?() || :no_wifi, 32 | true <- wifi_configured?() || :not_configured, 33 | true <- has_networks?() || :no_networks do 34 | # By this point we know there is a wlan interface available 35 | # and already configured with networks. This would normally 36 | # mean that you should then skip starting the WiFi wizard 37 | # here so that the device doesn't start the WiFi wizard after 38 | # every reboot. 39 | # 40 | # However, for the example we want to always run the 41 | # WiFi wizard on startup. Comment/remove the function below 42 | # if you want a more typical experience skipping the wizard 43 | # after it has been configured once. 44 | VintageNetWizard.run_wizard(on_exit: {__MODULE__, :on_wizard_exit, []}) 45 | else 46 | :no_wifi -> 47 | Logger.error( 48 | "[#{inspect(__MODULE__)}] Device does not support WiFi - Skipping wizard start" 49 | ) 50 | 51 | status -> 52 | info_message(status) 53 | VintageNetWizard.run_wizard(on_exit: {__MODULE__, :on_wizard_exit, []}) 54 | end 55 | end 56 | 57 | defp has_wifi?() do 58 | @ifname in VintageNet.all_interfaces() 59 | end 60 | 61 | defp wifi_configured?() do 62 | @ifname in VintageNet.configured_interfaces() 63 | end 64 | 65 | defp has_networks?() do 66 | VintageNet.get_configuration(@ifname).vintage_net_wifi.networks != [] 67 | end 68 | 69 | defp info_message(status) do 70 | msg = 71 | case status do 72 | :not_configured -> "WiFi has not been configured" 73 | :no_networks -> "WiFi was configured without any networks" 74 | end 75 | 76 | Logger.info("[#{inspect(__MODULE__)}] #{msg} - Starting WiFi Wizard") 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /hello_wifi/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule HelloWiFi.MixProject do 2 | use Mix.Project 3 | 4 | @app :hello_wifi 5 | @version "0.1.0" 6 | @all_targets [ 7 | :rpi, 8 | :rpi0, 9 | :rpi2, 10 | :rpi3, 11 | :rpi3a, 12 | :rpi4, 13 | :bbb, 14 | :osd32mp1, 15 | :x86_64, 16 | :grisp2, 17 | :mangopi_mq_pro 18 | ] 19 | 20 | def project do 21 | [ 22 | app: @app, 23 | version: @version, 24 | elixir: "~> 1.16", 25 | archives: [nerves_bootstrap: "~> 1.13"], 26 | start_permanent: Mix.env() == :prod, 27 | deps: deps(), 28 | releases: [{@app, release()}], 29 | preferred_cli_target: [run: :host, test: :host] 30 | ] 31 | end 32 | 33 | # Run "mix help compile.app" to learn about applications. 34 | def application do 35 | [ 36 | mod: {HelloWiFi, []}, 37 | extra_applications: [:logger, :runtime_tools] 38 | ] 39 | end 40 | 41 | # Run "mix help deps" to learn about dependencies. 42 | defp deps do 43 | [ 44 | # Dependencies for all targets 45 | {:circuits_gpio, "~> 2.0"}, 46 | {:nerves, "~> 1.10", runtime: false}, 47 | {:shoehorn, "~> 0.9.0"}, 48 | {:ring_logger, "~> 0.11.0"}, 49 | {:toolshed, "~> 0.4.0"}, 50 | 51 | # Dependencies for all targets except :host 52 | {:nerves_runtime, "~> 0.13.0", targets: @all_targets}, 53 | {:vintage_net_wizard, "~> 0.2", targets: @all_targets}, 54 | {:nerves_pack, "~> 0.7.0", targets: @all_targets}, 55 | 56 | # Dependencies for specific targets 57 | {:nerves_system_rpi, "~> 1.13", runtime: false, targets: :rpi}, 58 | {:nerves_system_rpi0, "~> 1.13", runtime: false, targets: :rpi0}, 59 | {:nerves_system_rpi2, "~> 1.13", runtime: false, targets: :rpi2}, 60 | {:nerves_system_rpi3, "~> 1.13", runtime: false, targets: :rpi3}, 61 | {:nerves_system_rpi3a, "~> 1.13", runtime: false, targets: :rpi3a}, 62 | {:nerves_system_rpi4, "~> 1.13", runtime: false, targets: :rpi4}, 63 | {:nerves_system_bbb, "~> 2.8", runtime: false, targets: :bbb}, 64 | {:nerves_system_osd32mp1, "~> 0.4", runtime: false, targets: :osd32mp1}, 65 | {:nerves_system_x86_64, "~> 1.13", runtime: false, targets: :x86_64}, 66 | {:nerves_system_grisp2, "~> 0.3", runtime: false, targets: :grisp2}, 67 | {:nerves_system_mangopi_mq_pro, "~> 0.4", runtime: false, targets: :mangopi_mq_pro} 68 | ] 69 | end 70 | 71 | def release do 72 | [ 73 | overwrite: true, 74 | # Erlang distribution is not started automatically. 75 | # See https://hexdocs.pm/nerves_pack/readme.html#erlang-distribution 76 | cookie: "#{@app}_cookie", 77 | include_erts: &Nerves.Release.erts/0, 78 | steps: [&Nerves.Release.init/1, :assemble], 79 | strip_beams: Mix.env() == :prod or [keep: ["Docs"]] 80 | ] 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /hello_wifi/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Add custom options here 2 | 3 | ## Distributed Erlang Options 4 | ## The cookie needs to be configured prior to vm boot for 5 | ## for read only filesystem. 6 | 7 | -setcookie <%= @release.options[:cookie] %> 8 | 9 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 10 | ## break handler and possibly exiting the VM. 11 | +Bc 12 | 13 | # Allow time warps so that the Erlang system time can more closely match the 14 | # OS system time. 15 | +C multi_time_warp 16 | 17 | ## Load code at system startup 18 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 19 | -mode embedded 20 | 21 | # Load code as per the boot script since not using archives 22 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 23 | -code_path_choice strict 24 | 25 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 26 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 27 | +sbwt none 28 | +sbwtdcpu none 29 | +sbwtdio none 30 | 31 | ## Save the shell history between reboots 32 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 33 | -kernel shell_history enabled 34 | 35 | ## Enable heartbeat monitoring of the Erlang runtime system 36 | -heart -env HEART_BEAT_TIMEOUT 30 37 | 38 | ## Start the Elixir shell 39 | 40 | -noshell 41 | -user elixir 42 | -run elixir start_cli 43 | 44 | ## Enable colors in the shell 45 | -elixir ansi_enabled true 46 | 47 | ## Options added after -extra are interpreted as plain arguments and can be 48 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 49 | ## interpreted by Elixir and anything afterwards is left around for other IEx 50 | ## and user applications. 51 | -extra --no-halt 52 | -- 53 | --dot-iex /etc/iex.exs 54 | -------------------------------------------------------------------------------- /hello_wifi/rootfs_overlay/etc/iex.exs: -------------------------------------------------------------------------------- 1 | NervesMOTD.print() 2 | 3 | # Add Toolshed helpers to the IEx session 4 | use Toolshed 5 | -------------------------------------------------------------------------------- /hello_zig/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: [ 4 | "{mix,.formatter}.exs", 5 | "{config,lib,test}/**/*.{ex,exs}", 6 | "rootfs_overlay/etc/iex.exs" 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /hello_zig/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # zig autogenerated code 20 | /.Elixir -------------------------------------------------------------------------------- /hello_zig/.requires-zig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_zig/.requires-zig -------------------------------------------------------------------------------- /hello_zig/.skip-mangopi_mq_pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/hello_zig/.skip-mangopi_mq_pro -------------------------------------------------------------------------------- /hello_zig/README.md: -------------------------------------------------------------------------------- 1 | # Hello Zig 2 | 3 | This example demonstrates a basic project for using the `Zigler` library to 4 | write low-level NIFs for a Nerves device. This project demonstrates 5 | [Zig](https://ziglang.org) code embedded into an Elixir module and how to call 6 | out to a zig code file *external* to the embedded Elixir module. 7 | 8 | Notably, no configuration is necessary to get proper cross-compilation between 9 | host and target architectures. 10 | 11 | The only thing you need to activate `zigler` is the dependency in your 12 | `mix.exs`: 13 | 14 | ```elixir 15 | {:zigler, "~> 0.10.1", runtime: false} 16 | ``` 17 | 18 | You might want to use Zig for any of the following things: 19 | 20 | * Low-level code that requires interaction with OS syscalls not available 21 | directly through the BEAM. 22 | * Performance-sensitive code which requires numerical computation 23 | * Wrapping an existing C ABI `.so` or `.a` library. 24 | 25 | ## Hardware 26 | 27 | The example below assumes a Raspberry Pi 3 connected over the Wifi. Other 28 | official and many unofficial Nerves targets work as well. Please post 29 | cross-compilation bug reports to the [zigler issue 30 | tracker](https://github.com/ityonemo/zigler/issues). 31 | 32 | ## How to use this repository 33 | 34 | 0. Go to the `hello_zig` directory 35 | 36 | 1. Set up your build environment 37 | 38 | ```shell 39 | # Specify the target hardware. See the mix.exs for options 40 | # RISC-V targets like mangopi_mq_pro might not work. 41 | export MIX_TARGET=rpi3 42 | 43 | # If using WiFi, you can set the SSID and password here 44 | export NERVES_NETWORK_SSID=your_wifi_name 45 | export NERVES_NETWORK_PSK=your_wifi_password 46 | ``` 47 | 48 | 2. Get dependencies, build firmware, and burn it to an SD card 49 | 50 | ```shell 51 | mix deps.get 52 | MIX_TARGET=host mix zig.get 53 | mix firmware 54 | mix firmware.burn 55 | ``` 56 | 57 | 3. Insert the SD card into your target board and power up 58 | 59 | 4. Wait to finish booting. 60 | 61 | 5. SSH into the board: `ssh nerves.local` 62 | 63 | 6. Execute `HelloZig.hello()` 64 | 65 | 7. You should see it output the atom `:world` 66 | 67 | ## Learn More 68 | 69 | - Official docs: https://hexdocs.pm/nerves/getting-started.html 70 | - Official website: https://nerves-project.org/ 71 | - Source: https://github.com/nerves-project/nerves 72 | 73 | - Zig language docs: https://ziglang.org/ 74 | - Zigler docs: https://hexdocs.pm/zigler 75 | -------------------------------------------------------------------------------- /hello_zig/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | config :hello_zig, target: Mix.target() 12 | 13 | # Customize non-Elixir parts of the firmware. See 14 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 15 | 16 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 17 | 18 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 19 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 20 | 21 | config :nerves, source_date_epoch: "1591241067" 22 | 23 | # Use Ringlogger as the logger backend and remove :console. 24 | # See https://hexdocs.pm/ring_logger/readme.html for more information on 25 | # configuring ring_logger. 26 | 27 | config :logger, backends: [RingLogger] 28 | 29 | if Mix.target() == :host do 30 | import_config "host.exs" 31 | else 32 | import_config "target.exs" 33 | end 34 | -------------------------------------------------------------------------------- /hello_zig/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | -------------------------------------------------------------------------------- /hello_zig/lib/hello_zig.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloZig do 2 | use Zig, otp_app: :hello_zig 3 | 4 | ~Z""" 5 | const beam = @import("beam"); 6 | const strings = @import("strings.zig"); 7 | 8 | pub fn hello(env: beam.env) beam.term { 9 | return beam.make(env, .{.ok, strings.world}, .{}); 10 | } 11 | """ 12 | end 13 | -------------------------------------------------------------------------------- /hello_zig/lib/hello_zig/application.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloZig.Application do 2 | # empty, placeholder application. 3 | @moduledoc false 4 | use Application 5 | 6 | def start(_type, _args) do 7 | opts = [strategy: :one_for_one, name: HelloZig.Supervisor] 8 | Supervisor.start_link([], opts) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /hello_zig/lib/strings.zig: -------------------------------------------------------------------------------- 1 | pub const world = "world"; 2 | -------------------------------------------------------------------------------- /hello_zig/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule HelloZig.MixProject do 2 | use Mix.Project 3 | 4 | @app :hello_zig 5 | @version "0.2.0" 6 | @all_targets [ 7 | :rpi, 8 | :rpi0, 9 | :rpi2, 10 | :rpi3, 11 | :rpi3a, 12 | :rpi4, 13 | :bbb, 14 | :osd32mp1, 15 | :x86_64, 16 | :grisp2, 17 | :mangopi_mq_pro 18 | ] 19 | 20 | def project do 21 | [ 22 | app: @app, 23 | version: @version, 24 | elixir: "~> 1.16", 25 | archives: [nerves_bootstrap: "~> 1.13"], 26 | start_permanent: Mix.env() == :prod, 27 | deps: deps(), 28 | releases: [{@app, release()}], 29 | preferred_cli_target: [run: :host, test: :host] 30 | ] 31 | end 32 | 33 | # Run "mix help compile.app" to learn about applications. 34 | def application do 35 | [ 36 | mod: {HelloZig.Application, []}, 37 | extra_applications: [:logger, :runtime_tools] 38 | ] 39 | end 40 | 41 | # Run "mix help deps" to learn about dependencies. 42 | defp deps do 43 | [ 44 | # Dependencies for all targets 45 | {:nerves, "~> 1.10", runtime: false}, 46 | {:shoehorn, "~> 0.9.0"}, 47 | {:ring_logger, "~> 0.11.0"}, 48 | {:toolshed, "~> 0.4.0"}, 49 | {:zigler, "~> 0.13.2", runtime: false}, 50 | 51 | # Dependencies for all targets except :host 52 | {:nerves_runtime, "~> 0.13.0", targets: @all_targets}, 53 | {:nerves_pack, "~> 0.7.0", targets: @all_targets}, 54 | 55 | # Dependencies for specific targets 56 | {:nerves_system_rpi, "~> 1.14", runtime: false, targets: :rpi}, 57 | {:nerves_system_rpi0, "~> 1.14", runtime: false, targets: :rpi0}, 58 | {:nerves_system_rpi2, "~> 1.14", runtime: false, targets: :rpi2}, 59 | {:nerves_system_rpi3, "~> 1.14", runtime: false, targets: :rpi3}, 60 | {:nerves_system_rpi3a, "~> 1.14", runtime: false, targets: :rpi3a}, 61 | {:nerves_system_rpi4, "~> 1.14", runtime: false, targets: :rpi4}, 62 | {:nerves_system_bbb, "~> 2.9", runtime: false, targets: :bbb}, 63 | {:nerves_system_osd32mp1, "~> 0.5", runtime: false, targets: :osd32mp1}, 64 | {:nerves_system_x86_64, "~> 1.14", runtime: false, targets: :x86_64}, 65 | {:nerves_system_grisp2, "~> 0.3", runtime: false, targets: :grisp2}, 66 | {:nerves_system_mangopi_mq_pro, "~> 0.4", runtime: false, targets: :mangopi_mq_pro} 67 | ] 68 | end 69 | 70 | def release do 71 | [ 72 | overwrite: true, 73 | # Erlang distribution is not started automatically. 74 | # See https://hexdocs.pm/nerves_pack/readme.html#erlang-distribution 75 | cookie: "#{@app}_cookie", 76 | include_erts: &Nerves.Release.erts/0, 77 | steps: [&Nerves.Release.init/1, :assemble], 78 | strip_beams: Mix.env() == :prod or [keep: ["Docs"]] 79 | ] 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /hello_zig/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Customize flags given to the VM: http://erlang.org/doc/man/erl.html 2 | 3 | ## Do not set -name or -sname here. Prefer configuring them at runtime 4 | ## Configure -setcookie in the mix.exs release section or at runtime 5 | 6 | ## Number of dirty schedulers doing IO work (file, sockets, and others) 7 | ##+SDio 5 8 | 9 | ## Increase number of concurrent ports/sockets 10 | ##+Q 65536 11 | 12 | ## Tweak GC to run more often 13 | ##-env ERL_FULLSWEEP_AFTER 10 14 | 15 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 16 | ## break handler and possibly exiting the VM. 17 | +Bc 18 | 19 | # Allow time warps so that the Erlang system time can more closely match the 20 | # OS system time. 21 | +C multi_time_warp 22 | 23 | ## Load code at system startup 24 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 25 | -mode embedded 26 | 27 | # Load code as per the boot script since not using archives 28 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 29 | -code_path_choice strict 30 | 31 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 32 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 33 | +sbwt none 34 | +sbwtdcpu none 35 | +sbwtdio none 36 | 37 | ## Save the shell history between reboots 38 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 39 | -kernel shell_history enabled 40 | 41 | ## Enable heartbeat monitoring of the Erlang runtime system 42 | -heart -env HEART_BEAT_TIMEOUT 30 43 | 44 | ## Start the Elixir shell 45 | 46 | -noshell 47 | -user elixir 48 | -run elixir start_cli 49 | 50 | 51 | ## Enable colors in the shell 52 | -elixir ansi_enabled true 53 | 54 | ## Options added after -extra are interpreted as plain arguments and can be 55 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 56 | ## interpreted by Elixir and anything afterwards is left around for other IEx 57 | ## and user applications. 58 | -extra --no-halt 59 | -- 60 | --dot-iex /etc/iex.exs 61 | -------------------------------------------------------------------------------- /hello_zig/rootfs_overlay/etc/iex.exs: -------------------------------------------------------------------------------- 1 | NervesMOTD.print() 2 | 3 | # Add Toolshed helpers to the IEx session 4 | use Toolshed 5 | -------------------------------------------------------------------------------- /hello_zig/test/hello_zig_test.exs: -------------------------------------------------------------------------------- 1 | defmodule HelloZigTest do 2 | use ExUnit.Case 3 | 4 | test "greets the world" do 5 | assert HelloZig.hello() == :world 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /hello_zig/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /minimal/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: [ 4 | "{mix,.formatter}.exs", 5 | "{config,lib,test}/**/*.{ex,exs}", 6 | "rootfs_overlay/etc/iex.exs" 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /minimal/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | -------------------------------------------------------------------------------- /minimal/README.md: -------------------------------------------------------------------------------- 1 | # Minimal 2 | 3 | This is a minimal Nerves application. It doesn't bring up networking or support 4 | firmware updates. You will get an IEx prompt on the console, though! 5 | 6 | It's more useful for seeing what's included in the base Nerves systems. You can 7 | create your own custom systems to reduce the footprint further, but this will be 8 | what we give you out of the box. In some cases, namely for the Raspberry Pi, 9 | it's quite a bit and there's lots of room for reduction. 10 | 11 | -------------------------------------------------------------------------------- /minimal/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 12 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 13 | 14 | config :nerves, source_date_epoch: "1578177235" 15 | 16 | if Mix.target() == :host do 17 | import_config "host.exs" 18 | else 19 | import_config "target.exs" 20 | end 21 | -------------------------------------------------------------------------------- /minimal/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | -------------------------------------------------------------------------------- /minimal/config/target.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Use shoehorn to start the main application. See the shoehorn 4 | # docs for separating out critical OTP applications such as those 5 | # involved with firmware updates. 6 | 7 | config :shoehorn, init: [:nerves_runtime] 8 | 9 | # Erlinit can be configured without a rootfs_overlay. See 10 | # https://github.com/nerves-project/erlinit/ for more information on 11 | # configuring erlinit. 12 | 13 | config :nerves, :erlinit, update_clock: true 14 | 15 | # Import target specific config. This must remain at the bottom 16 | # of this file so it overrides the configuration defined above. 17 | # Uncomment to use target specific configurations 18 | 19 | # import_config "#{Mix.target()}.exs" 20 | -------------------------------------------------------------------------------- /minimal/lib/minimal.ex: -------------------------------------------------------------------------------- 1 | defmodule Minimal do 2 | @moduledoc """ 3 | Documentation for Minimal. 4 | """ 5 | 6 | @doc """ 7 | Hello world. 8 | 9 | ## Examples 10 | 11 | iex> Minimal.hello 12 | :world 13 | 14 | """ 15 | def hello do 16 | :world 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /minimal/lib/minimal/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Minimal.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | def start(_type, _args) do 9 | # See https://hexdocs.pm/elixir/Supervisor.html 10 | # for other strategies and supported options 11 | opts = [strategy: :one_for_one, name: Minimal.Supervisor] 12 | 13 | children = 14 | [ 15 | # Children for all targets 16 | # Starts a worker by calling: Minimal.Worker.start_link(arg) 17 | # {Minimal.Worker, arg}, 18 | ] ++ children(target()) 19 | 20 | Supervisor.start_link(children, opts) 21 | end 22 | 23 | # List all child processes to be supervised 24 | def children(:host) do 25 | [ 26 | # Children that only run on the host 27 | # Starts a worker by calling: Minimal.Worker.start_link(arg) 28 | # {Minimal.Worker, arg}, 29 | ] 30 | end 31 | 32 | def children(_target) do 33 | [ 34 | # Children for all targets except host 35 | # Starts a worker by calling: Minimal.Worker.start_link(arg) 36 | # {Minimal.Worker, arg}, 37 | ] 38 | end 39 | 40 | def target() do 41 | Application.get_env(:minimal, :target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /minimal/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Minimal.MixProject do 2 | use Mix.Project 3 | 4 | @app :minimal 5 | @version "0.1.0" 6 | @all_targets [ 7 | :rpi, 8 | :rpi0, 9 | :rpi2, 10 | :rpi3, 11 | :rpi3a, 12 | :rpi4, 13 | :bbb, 14 | :osd32mp1, 15 | :x86_64, 16 | :grisp2, 17 | :mangopi_mq_pro 18 | ] 19 | 20 | def project do 21 | [ 22 | app: @app, 23 | version: @version, 24 | elixir: "~> 1.16", 25 | archives: [nerves_bootstrap: "~> 1.13"], 26 | start_permanent: Mix.env() == :prod, 27 | deps: deps(), 28 | releases: [{@app, release()}], 29 | preferred_cli_target: [run: :host, test: :host] 30 | ] 31 | end 32 | 33 | # Run "mix help compile.app" to learn about applications. 34 | def application do 35 | [ 36 | mod: {Minimal.Application, []}, 37 | extra_applications: [:logger] 38 | ] 39 | end 40 | 41 | # Run "mix help deps" to learn about dependencies. 42 | defp deps do 43 | [ 44 | # Dependencies for all targets 45 | {:nerves, "~> 1.10", runtime: false}, 46 | {:shoehorn, "~> 0.9.0"}, 47 | 48 | # Dependencies for all targets except :host 49 | {:nerves_runtime, "~> 0.13.0", targets: @all_targets}, 50 | 51 | # Dependencies for specific targets 52 | {:nerves_system_rpi, "~> 1.13", runtime: false, targets: :rpi}, 53 | {:nerves_system_rpi0, "~> 1.13", runtime: false, targets: :rpi0}, 54 | {:nerves_system_rpi2, "~> 1.13", runtime: false, targets: :rpi2}, 55 | {:nerves_system_rpi3, "~> 1.13", runtime: false, targets: :rpi3}, 56 | {:nerves_system_rpi3a, "~> 1.13", runtime: false, targets: :rpi3a}, 57 | {:nerves_system_rpi4, "~> 1.13", runtime: false, targets: :rpi4}, 58 | {:nerves_system_bbb, "~> 2.8", runtime: false, targets: :bbb}, 59 | {:nerves_system_osd32mp1, "~> 0.4", runtime: false, targets: :osd32mp1}, 60 | {:nerves_system_x86_64, "~> 1.13", runtime: false, targets: :x86_64}, 61 | {:nerves_system_grisp2, "~> 0.3", runtime: false, targets: :grisp2}, 62 | {:nerves_system_mangopi_mq_pro, "~> 0.4", runtime: false, targets: :mangopi_mq_pro} 63 | ] 64 | end 65 | 66 | def release do 67 | [ 68 | overwrite: true, 69 | # Erlang distribution is not started automatically. 70 | # See https://hexdocs.pm/nerves_pack/readme.html#erlang-distribution 71 | cookie: "#{@app}_cookie", 72 | include_erts: &Nerves.Release.erts/0, 73 | steps: [&Nerves.Release.init/1, :assemble], 74 | strip_beams: Mix.env() == :prod or [keep: ["Docs"]] 75 | ] 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /minimal/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Add custom options here 2 | 3 | ## Distributed Erlang Options 4 | ## The cookie needs to be configured prior to vm boot for 5 | ## for read only filesystem. 6 | 7 | -setcookie <%= @release.options[:cookie] %> 8 | 9 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 10 | ## break handler and possibly exiting the VM. 11 | +Bc 12 | 13 | # Allow time warps so that the Erlang system time can more closely match the 14 | # OS system time. 15 | +C multi_time_warp 16 | 17 | ## Load code at system startup 18 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 19 | -mode embedded 20 | 21 | # Load code as per the boot script since not using archives 22 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 23 | -code_path_choice strict 24 | 25 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 26 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 27 | +sbwt none 28 | +sbwtdcpu none 29 | +sbwtdio none 30 | 31 | ## Save the shell history between reboots 32 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 33 | -kernel shell_history enabled 34 | 35 | ## Enable heartbeat monitoring of the Erlang runtime system 36 | -heart -env HEART_BEAT_TIMEOUT 30 37 | 38 | ## Start the Elixir shell 39 | 40 | -noshell 41 | -user elixir 42 | -run elixir start_cli 43 | 44 | ## Enable colors in the shell 45 | -elixir ansi_enabled true 46 | 47 | ## Options added after -extra are interpreted as plain arguments and can be 48 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 49 | ## interpreted by Elixir and anything afterwards is left around for other IEx 50 | ## and user applications. 51 | -extra --no-halt 52 | -- 53 | --dot-iex /etc/iex.exs 54 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: [ 4 | "{mix,.formatter}.exs", 5 | "{config,lib,test}/**/*.{ex,exs}", 6 | "rootfs_overlay/etc/iex.exs" 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/README.md: -------------------------------------------------------------------------------- 1 | # Firmware 2 | 3 | **TODO: Add description** 4 | 5 | ## Targets 6 | 7 | Nerves applications produce images for hardware targets based on the 8 | `MIX_TARGET` environment variable. If `MIX_TARGET` is unset, `mix` builds an 9 | image that runs on the host (e.g., your laptop). This is useful for executing 10 | logic tests, running utilities, and debugging. Other targets are represented by 11 | a short name like `rpi3` that maps to a Nerves system image for that platform. 12 | All of this logic is in the generated `mix.exs` and may be customized. For more 13 | information about targets see: 14 | 15 | https://hexdocs.pm/nerves/targets.html#content 16 | 17 | ## Getting Started 18 | 19 | To start your Nerves app: 20 | * `export MIX_TARGET=my_target` or prefix every command with 21 | `MIX_TARGET=my_target`. For example, `MIX_TARGET=rpi3` 22 | * Install dependencies with `mix deps.get` 23 | * Create firmware with `mix firmware` 24 | * Burn to an SD card with `mix firmware.burn` 25 | 26 | ## Learn more 27 | 28 | * Official docs: https://hexdocs.pm/nerves/getting-started.html 29 | * Official website: https://nerves-project.org/ 30 | * Forum: https://elixirforum.com/c/nerves-forum 31 | * Discussion Slack elixir-lang #nerves ([Invite](https://elixir-slackin.herokuapp.com/)) 32 | * Source: https://github.com/nerves-project/nerves 33 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application and its 2 | # dependencies. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | import Config 7 | 8 | # Enable the Nerves integration with Mix 9 | Application.start(:nerves_bootstrap) 10 | 11 | config :firmware, target: Mix.target() 12 | 13 | # Customize non-Elixir parts of the firmware. See 14 | # https://hexdocs.pm/nerves/advanced-configuration.html for details. 15 | 16 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay" 17 | 18 | # Set the SOURCE_DATE_EPOCH date for reproducible builds. 19 | # See https://reproducible-builds.org/docs/source-date-epoch/ for more information 20 | 21 | config :nerves, source_date_epoch: "1591379755" 22 | 23 | # Use Ringlogger as the logger backend and remove :console. 24 | # See https://hexdocs.pm/ring_logger/readme.html for more information on 25 | # configuring ring_logger. 26 | 27 | config :logger, backends: [RingLogger] 28 | 29 | config :ui, 30 | ecto_repos: [Ui.Repo] 31 | 32 | # Use Jason for JSON parsing in Phoenix 33 | config :phoenix, :json_library, Jason 34 | 35 | if Mix.target() == :host do 36 | import_config "host.exs" 37 | else 38 | import_config "target.exs" 39 | end 40 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/config/host.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Add configuration that is only needed when running on the host here. 4 | 5 | config :ui, Ui.Repo, 6 | database: Path.expand("../ui_dev.db", Path.dirname(__ENV__.file)), 7 | pool_size: 5, 8 | show_sensitive_data_on_connection_error: true 9 | 10 | import_config "../../ui/config/config.exs" 11 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/lib/firmware.ex: -------------------------------------------------------------------------------- 1 | defmodule Firmware do 2 | @moduledoc """ 3 | Documentation for Firmware. 4 | """ 5 | 6 | @doc """ 7 | Hello world. 8 | 9 | ## Examples 10 | 11 | iex> Firmware.hello 12 | :world 13 | 14 | """ 15 | def hello do 16 | :world 17 | end 18 | 19 | defdelegate hello_ui, to: Ui, as: :hello 20 | defdelegate list_users, to: Ui.Accounts 21 | end 22 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/lib/firmware/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Firmware.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | # See https://hexdocs.pm/elixir/Supervisor.html 11 | # for other strategies and supported options 12 | opts = [strategy: :one_for_one, name: Firmware.Supervisor] 13 | 14 | children = 15 | [ 16 | {Task, &Firmware.MigrationHelpers.migrate/0} 17 | # Children for all targets 18 | # Starts a worker by calling: Firmware.Worker.start_link(arg) 19 | # {Firmware.Worker, arg}, 20 | ] ++ children(target()) 21 | 22 | Supervisor.start_link(children, opts) 23 | end 24 | 25 | # List all child processes to be supervised 26 | def children(:host) do 27 | [ 28 | # Children that only run on the host 29 | # Starts a worker by calling: Firmware.Worker.start_link(arg) 30 | # {Firmware.Worker, arg}, 31 | ] 32 | end 33 | 34 | def children(_target) do 35 | [ 36 | # Children for all targets except host 37 | # Starts a worker by calling: Firmware.Worker.start_link(arg) 38 | # {Firmware.Worker, arg}, 39 | ] 40 | end 41 | 42 | def target() do 43 | Application.get_env(:firmware, :target) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/lib/firmware/migration_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule Firmware.MigrationHelpers do 2 | @app :ui 3 | 4 | def migrate() do 5 | for repo <- repos() do 6 | {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) 7 | end 8 | end 9 | 10 | def rollback(repo, version) do 11 | {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) 12 | end 13 | 14 | defp repos() do 15 | _ = Application.load(@app) 16 | Application.fetch_env!(@app, :ecto_repos) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/rel/vm.args.eex: -------------------------------------------------------------------------------- 1 | ## Customize flags given to the VM: http://erlang.org/doc/man/erl.html 2 | 3 | ## Do not set -name or -sname here. Prefer configuring them at runtime 4 | ## Configure -setcookie in the mix.exs release section or at runtime 5 | 6 | ## Number of dirty schedulers doing IO work (file, sockets, and others) 7 | ##+SDio 5 8 | 9 | ## Increase number of concurrent ports/sockets 10 | ##+Q 65536 11 | 12 | ## Tweak GC to run more often 13 | ##-env ERL_FULLSWEEP_AFTER 10 14 | 15 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's 16 | ## break handler and possibly exiting the VM. 17 | +Bc 18 | 19 | # Allow time warps so that the Erlang system time can more closely match the 20 | # OS system time. 21 | +C multi_time_warp 22 | 23 | ## Load code at system startup 24 | ## See http://erlang.org/doc/system_principles/system_principles.html#code-loading-strategy 25 | -mode embedded 26 | 27 | # Load code as per the boot script since not using archives 28 | # See https://www.erlang.org/doc/man/init.html#command-line-flags 29 | -code_path_choice strict 30 | 31 | ## Disable scheduler busy wait to reduce idle CPU usage and avoid delaying 32 | ## other OS processes. See http://erlang.org/doc/man/erl.html#+sbwt 33 | +sbwt none 34 | +sbwtdcpu none 35 | +sbwtdio none 36 | 37 | ## Save the shell history between reboots 38 | ## See http://erlang.org/doc/man/kernel_app.html for additional options 39 | -kernel shell_history enabled 40 | 41 | ## Enable heartbeat monitoring of the Erlang runtime system 42 | -heart -env HEART_BEAT_TIMEOUT 30 43 | 44 | ## Start the Elixir shell 45 | 46 | -noshell 47 | -user elixir 48 | -run elixir start_cli 49 | 50 | ## Enable colors in the shell 51 | -elixir ansi_enabled true 52 | 53 | ## Options added after -extra are interpreted as plain arguments and can be 54 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are 55 | ## interpreted by Elixir and anything afterwards is left around for other IEx 56 | ## and user applications. 57 | -extra --no-halt 58 | -- 59 | --dot-iex /etc/iex.exs 60 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/rootfs_overlay/etc/iex.exs: -------------------------------------------------------------------------------- 1 | NervesMOTD.print() 2 | 3 | # Add Toolshed helpers to the IEx session 4 | use Toolshed 5 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/test/firmware_test.exs: -------------------------------------------------------------------------------- 1 | defmodule FirmwareTest do 2 | use ExUnit.Case 3 | doctest Firmware 4 | 5 | test "greets the world" do 6 | assert Firmware.hello() == :world 7 | end 8 | 9 | test "greets the Ui" do 10 | assert Firmware.hello_ui() == :world 11 | end 12 | 13 | test "gets a list of users" do 14 | assert Firmware.list_users() == [] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /poncho_phoenix/firmware/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto, :ecto_sql, :phoenix], 3 | subdirectories: ["priv/*/migrations"], 4 | plugins: [Phoenix.LiveView.HTMLFormatter], 5 | inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"] 6 | ] 7 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Temporary files, for example, from tests. 23 | /tmp/ 24 | 25 | # Ignore package tarball (built via "mix hex.build"). 26 | ui-*.tar 27 | 28 | # Ignore assets that are produced by build tools. 29 | /priv/static/assets/ 30 | 31 | # Ignore digested assets cache. 32 | /priv/static/cache_manifest.json 33 | 34 | # In case you use Node.js/npm, you want to ignore these. 35 | npm-debug.log 36 | /assets/node_modules/ 37 | 38 | # Database files 39 | *.db 40 | *.db-* 41 | 42 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/README.md: -------------------------------------------------------------------------------- 1 | # Ui 2 | 3 | To start your Phoenix server: 4 | 5 | * Install dependencies with `mix deps.get` 6 | * Create and migrate your database with `mix ecto.setup` 7 | * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` 8 | 9 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 10 | 11 | Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). 12 | 13 | ## Learn more 14 | 15 | * Official website: https://www.phoenixframework.org/ 16 | * Guides: https://hexdocs.pm/phoenix/overview.html 17 | * Docs: https://hexdocs.pm/phoenix 18 | * Forum: https://elixirforum.com/c/phoenix-forum 19 | * Source: https://github.com/phoenixframework/phoenix 20 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/assets/css/app.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; 4 | 5 | /* This file is for your main application CSS */ 6 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/assets/js/app.js: -------------------------------------------------------------------------------- 1 | // If you want to use Phoenix channels, run `mix help phx.gen.channel` 2 | // to get started and then uncomment the line below. 3 | // import "./user_socket.js" 4 | 5 | // You can include dependencies in two ways. 6 | // 7 | // The simplest option is to put them in assets/vendor and 8 | // import them using relative paths: 9 | // 10 | // import "../vendor/some-package.js" 11 | // 12 | // Alternatively, you can `npm install some-package --prefix assets` and import 13 | // them using a path starting with the package name: 14 | // 15 | // import "some-package" 16 | // 17 | 18 | // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. 19 | import "phoenix_html" 20 | // Establish Phoenix Socket and LiveView configuration. 21 | import {Socket} from "phoenix" 22 | import {LiveSocket} from "phoenix_live_view" 23 | import topbar from "../vendor/topbar" 24 | 25 | let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") 26 | let liveSocket = new LiveSocket("/live", Socket, { 27 | longPollFallbackMs: 2500, 28 | params: {_csrf_token: csrfToken} 29 | }) 30 | 31 | // Show progress bar on live navigation and form submits 32 | topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) 33 | window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) 34 | window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) 35 | 36 | // connect if there are any LiveViews on the page 37 | liveSocket.connect() 38 | 39 | // expose liveSocket on window for web console debug logs and latency simulation: 40 | // >> liveSocket.enableDebug() 41 | // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session 42 | // >> liveSocket.disableLatencySim() 43 | window.liveSocket = liveSocket 44 | 45 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Config module. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | 7 | # General application configuration 8 | import Config 9 | 10 | config :ui, 11 | ecto_repos: [Ui.Repo] 12 | 13 | # Configures the endpoint 14 | config :ui, UiWeb.Endpoint, 15 | url: [host: "localhost"], 16 | render_errors: [view: UiWeb.ErrorView, accepts: ~w(html json), layout: false], 17 | pubsub_server: Ui.PubSub, 18 | live_view: [signing_salt: "4jVC64mw"] 19 | 20 | # Configure esbuild (the version is required) 21 | config :esbuild, 22 | version: "0.12.18", 23 | default: [ 24 | args: 25 | ~w(js/app.js --bundle --target=es2016 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), 26 | cd: Path.expand("../assets", __DIR__), 27 | env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} 28 | ] 29 | 30 | # Configure tailwind (the version is required) 31 | config :tailwind, 32 | version: "3.3.2", 33 | default: [ 34 | args: ~w( 35 | --config=tailwind.config.js 36 | --input=css/app.css 37 | --output=../priv/static/assets/app.css 38 | ), 39 | cd: Path.expand("../assets", __DIR__) 40 | ] 41 | 42 | # Configures Elixir's Logger 43 | config :logger, :console, 44 | format: "$time $metadata[$level] $message\n", 45 | metadata: [:request_id] 46 | 47 | # Use Jason for JSON parsing in Phoenix 48 | config :phoenix, :json_library, Jason 49 | 50 | # Import environment specific config. This must remain at the bottom 51 | # of this file so it overrides the configuration defined above. 52 | import_config "#{config_env()}.exs" 53 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/config/dev.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Configure your database 4 | config :ui, Ui.Repo, 5 | database: Path.expand("../ui_dev.db", Path.dirname(__ENV__.file)), 6 | pool_size: 5, 7 | show_sensitive_data_on_connection_error: true 8 | 9 | # For development, we disable any cache and enable 10 | # debugging and code reloading. 11 | # 12 | # The watchers configuration can be used to run external 13 | # watchers to your application. For example, we can use it 14 | # to bundle .js and .css sources. 15 | config :ui, UiWeb.Endpoint, 16 | # Binding to loopback ipv4 address prevents access from other machines. 17 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. 18 | http: [ip: {127, 0, 0, 1}, port: 4000], 19 | check_origin: false, 20 | code_reloader: true, 21 | debug_errors: true, 22 | secret_key_base: "O2Qalbsez/Ei1FqotFjkfUpep53aAyMjotAPfe2XCb3Cw2fi4jM0y4rhTUBlJpvM", 23 | watchers: [ 24 | esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}, 25 | tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} 26 | ] 27 | 28 | # ## SSL Support 29 | # 30 | # In order to use HTTPS in development, a self-signed 31 | # certificate can be generated by running the following 32 | # Mix task: 33 | # 34 | # mix phx.gen.cert 35 | # 36 | # Note that this task requires Erlang/OTP 20 or later. 37 | # Run `mix help phx.gen.cert` for more information. 38 | # 39 | # The `http:` config above can be replaced with: 40 | # 41 | # https: [ 42 | # port: 4001, 43 | # cipher_suite: :strong, 44 | # keyfile: "priv/cert/selfsigned_key.pem", 45 | # certfile: "priv/cert/selfsigned.pem" 46 | # ], 47 | # 48 | # If desired, both `http:` and `https:` keys can be 49 | # configured to run both http and https servers on 50 | # different ports. 51 | 52 | # Watch static and templates for browser reloading. 53 | config :ui, UiWeb.Endpoint, 54 | live_reload: [ 55 | patterns: [ 56 | ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$", 57 | ~r"priv/gettext/.*(po)$", 58 | ~r"lib/ui_web/(controllers|live|components)/.*(ex|heex)$" 59 | ] 60 | ] 61 | 62 | # Do not include metadata nor timestamps in development logs 63 | config :logger, :console, format: "[$level] $message\n" 64 | 65 | # Set a higher stacktrace during development. Avoid configuring such 66 | # in production as building large stacktraces may be expensive. 67 | config :phoenix, :stacktrace_depth, 20 68 | 69 | # Initialize plugs at runtime for faster development compilation 70 | config :phoenix, :plug_init_mode, :runtime 71 | 72 | config :phoenix_live_view, 73 | # Include HEEx debug annotations as HTML comments in rendered markup 74 | debug_heex_annotations: true, 75 | # Enable helpful, but potentially expensive runtime checks 76 | enable_expensive_runtime_checks: true 77 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/config/prod.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For production, don't forget to configure the url host 4 | # to something meaningful, Phoenix uses this information 5 | # when generating URLs. 6 | # 7 | # Note we also include the path to a cache manifest 8 | # containing the digested version of static files. This 9 | # manifest is generated by the `mix phx.digest` task, 10 | # which you should run after static files are built and 11 | # before starting your production server. 12 | config :ui, UiWeb.Endpoint, 13 | url: [host: "example.com", port: 80], 14 | cache_static_manifest: "priv/static/cache_manifest.json" 15 | 16 | # Do not print debug messages in production 17 | config :logger, level: :info 18 | 19 | # ## SSL Support 20 | # 21 | # To get SSL working, you will need to add the `https` key 22 | # to the previous section and set your `:url` port to 443: 23 | # 24 | # config :ui, UiWeb.Endpoint, 25 | # ..., 26 | # url: [host: "example.com", port: 443], 27 | # https: [ 28 | # ..., 29 | # port: 443, 30 | # cipher_suite: :strong, 31 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 32 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") 33 | # ] 34 | # 35 | # The `cipher_suite` is set to `:strong` to support only the 36 | # latest and more secure SSL ciphers. This means old browsers 37 | # and clients may not be supported. You can set it to 38 | # `:compatible` for wider support. 39 | # 40 | # `:keyfile` and `:certfile` expect an absolute path to the key 41 | # and cert in disk or a relative path inside priv, for example 42 | # "priv/ssl/server.key". For all supported SSL configuration 43 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 44 | # 45 | # We also recommend setting `force_ssl` in your endpoint, ensuring 46 | # no data is ever sent via http, always redirecting to https: 47 | # 48 | # config :ui, UiWeb.Endpoint, 49 | # force_ssl: [hsts: true] 50 | # 51 | # Check `Plug.SSL` for all available options in `force_ssl`. 52 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # config/runtime.exs is executed for all environments, including 4 | # during releases. It is executed after compilation and before the 5 | # system starts, so it is typically used to load production configuration 6 | # and secrets from environment variables or elsewhere. Do not define 7 | # any compile-time configuration in here, as it won't be applied. 8 | # The block below contains prod specific runtime configuration. 9 | if config_env() == :prod do 10 | database_path = 11 | System.get_env("DATABASE_PATH") || 12 | raise """ 13 | environment variable DATABASE_PATH is missing. 14 | For example: /etc/ui/ui.db 15 | """ 16 | 17 | config :ui, Ui.Repo, 18 | database: database_path, 19 | pool_size: String.to_integer(System.get_env("POOL_SIZE") || "5") 20 | 21 | # The secret key base is used to sign/encrypt cookies and other secrets. 22 | # A default value is used in config/dev.exs and config/test.exs but you 23 | # want to use a different value for prod and you most likely don't want 24 | # to check this value into version control, so we use an environment 25 | # variable instead. 26 | secret_key_base = 27 | System.get_env("SECRET_KEY_BASE") || 28 | raise """ 29 | environment variable SECRET_KEY_BASE is missing. 30 | You can generate one by calling: mix phx.gen.secret 31 | """ 32 | 33 | config :ui, UiWeb.Endpoint, 34 | http: [ 35 | # Enable IPv6 and bind on all interfaces. 36 | # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. 37 | # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html 38 | # for details about using IPv6 vs IPv4 and loopback vs public addresses. 39 | ip: {0, 0, 0, 0, 0, 0, 0, 0}, 40 | port: String.to_integer(System.get_env("PORT") || "4000") 41 | ], 42 | secret_key_base: secret_key_base 43 | 44 | # ## Using releases 45 | # 46 | # If you are doing OTP releases, you need to instruct Phoenix 47 | # to start each relevant endpoint: 48 | # 49 | # config :ui, UiWeb.Endpoint, server: true 50 | # 51 | # Then you can assemble a release by calling `mix release`. 52 | # See `mix help release` for more information. 53 | end 54 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/config/test.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Configure your database 4 | # 5 | # The MIX_TEST_PARTITION environment variable can be used 6 | # to provide built-in test partitioning in CI environment. 7 | # Run `mix help test` for more information. 8 | config :ui, Ui.Repo, 9 | database: Path.expand("../ui_test.db", Path.dirname(__ENV__.file)), 10 | pool_size: 5, 11 | pool: Ecto.Adapters.SQL.Sandbox 12 | 13 | # We don't run a server during test. If one is required, 14 | # you can enable the server option below. 15 | config :ui, UiWeb.Endpoint, 16 | http: [ip: {127, 0, 0, 1}, port: 4002], 17 | secret_key_base: "UWZ5ec8xV6fJbwHAVRAts5TxMH8dVS6TXbvrsa93dLxBvA2gkC9cvNSNP+EIQHfW", 18 | server: false 19 | 20 | # Print only warnings and errors during test 21 | config :logger, level: :warning 22 | 23 | # Initialize plugs at runtime for faster test compilation 24 | config :phoenix, :plug_init_mode, :runtime 25 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui.ex: -------------------------------------------------------------------------------- 1 | defmodule Ui do 2 | @moduledoc """ 3 | Ui keeps the contexts that define your domain 4 | and business logic. 5 | 6 | Contexts are also responsible for managing your data, regardless 7 | if it comes from the database, an external API or others. 8 | """ 9 | 10 | def hello, do: :world 11 | end 12 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui/accounts.ex: -------------------------------------------------------------------------------- 1 | defmodule Ui.Accounts do 2 | @moduledoc """ 3 | The Accounts context. 4 | """ 5 | 6 | import Ecto.Query, warn: false 7 | alias Ui.Repo 8 | 9 | alias Ui.Accounts.User 10 | 11 | @doc """ 12 | Returns the list of users. 13 | 14 | ## Examples 15 | 16 | iex> list_users() 17 | [%User{}, ...] 18 | 19 | """ 20 | def list_users do 21 | Repo.all(User) 22 | end 23 | 24 | @doc """ 25 | Gets a single user. 26 | 27 | Raises `Ecto.NoResultsError` if the User does not exist. 28 | 29 | ## Examples 30 | 31 | iex> get_user!(123) 32 | %User{} 33 | 34 | iex> get_user!(456) 35 | ** (Ecto.NoResultsError) 36 | 37 | """ 38 | def get_user!(id), do: Repo.get!(User, id) 39 | 40 | @doc """ 41 | Creates a user. 42 | 43 | ## Examples 44 | 45 | iex> create_user(%{field: value}) 46 | {:ok, %User{}} 47 | 48 | iex> create_user(%{field: bad_value}) 49 | {:error, %Ecto.Changeset{}} 50 | 51 | """ 52 | def create_user(attrs \\ %{}) do 53 | %User{} 54 | |> User.changeset(attrs) 55 | |> Repo.insert() 56 | end 57 | 58 | @doc """ 59 | Updates a user. 60 | 61 | ## Examples 62 | 63 | iex> update_user(user, %{field: new_value}) 64 | {:ok, %User{}} 65 | 66 | iex> update_user(user, %{field: bad_value}) 67 | {:error, %Ecto.Changeset{}} 68 | 69 | """ 70 | def update_user(%User{} = user, attrs) do 71 | user 72 | |> User.changeset(attrs) 73 | |> Repo.update() 74 | end 75 | 76 | @doc """ 77 | Deletes a user. 78 | 79 | ## Examples 80 | 81 | iex> delete_user(user) 82 | {:ok, %User{}} 83 | 84 | iex> delete_user(user) 85 | {:error, %Ecto.Changeset{}} 86 | 87 | """ 88 | def delete_user(%User{} = user) do 89 | Repo.delete(user) 90 | end 91 | 92 | @doc """ 93 | Returns an `%Ecto.Changeset{}` for tracking user changes. 94 | 95 | ## Examples 96 | 97 | iex> change_user(user) 98 | %Ecto.Changeset{data: %User{}} 99 | 100 | """ 101 | def change_user(%User{} = user, attrs \\ %{}) do 102 | User.changeset(user, attrs) 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui/accounts/user.ex: -------------------------------------------------------------------------------- 1 | defmodule Ui.Accounts.User do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | 5 | schema "users" do 6 | field :age, :integer 7 | field :name, :string 8 | 9 | timestamps() 10 | end 11 | 12 | @doc false 13 | def changeset(user, attrs) do 14 | user 15 | |> cast(attrs, [:name, :age]) 16 | |> validate_required([:name, :age]) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Ui.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | UiWeb.Telemetry, 12 | Ui.Repo, 13 | {DNSCluster, query: Application.get_env(:ui, :dns_cluster_query) || :ignore}, 14 | {Phoenix.PubSub, name: Ui.PubSub}, 15 | # Start a worker by calling: Ui.Worker.start_link(arg) 16 | # {Ui.Worker, arg}, 17 | # Start to serve requests, typically the last entry 18 | UiWeb.Endpoint 19 | ] 20 | 21 | # See https://hexdocs.pm/elixir/Supervisor.html 22 | # for other strategies and supported options 23 | opts = [strategy: :one_for_one, name: Ui.Supervisor] 24 | Supervisor.start_link(children, opts) 25 | end 26 | 27 | # Tell Phoenix to update the endpoint configuration 28 | # whenever the application is updated. 29 | @impl true 30 | def config_change(changed, _new, removed) do 31 | UiWeb.Endpoint.config_change(changed, removed) 32 | :ok 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule Ui.Repo do 2 | use Ecto.Repo, 3 | otp_app: :ui, 4 | adapter: Ecto.Adapters.SQLite3 5 | end 6 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/components/layouts.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.Layouts do 2 | use UiWeb, :html 3 | 4 | embed_templates "layouts/*" 5 | end 6 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/components/layouts/app.html.heex: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 |

8 | v<%= Application.spec(:phoenix, :vsn) %> 9 |

10 |
11 | 25 |
26 |
27 |
28 |
29 | <.flash_group flash={@flash} /> 30 | <%= @inner_content %> 31 |
32 |
33 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/components/layouts/root.html.heex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <.live_title suffix=" · Phoenix Framework"> 8 | <%= assigns[:page_title] || "Ui" %> 9 | 10 | 11 | 13 | 14 | 15 | <%= @inner_content %> 16 | 17 | 18 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/controllers/error_html.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.ErrorHTML do 2 | use UiWeb, :html 3 | 4 | # If you want to customize your error pages, 5 | # uncomment the embed_templates/1 call below 6 | # and add pages to the error directory: 7 | # 8 | # * lib/ui_web/controllers/error_html/404.html.heex 9 | # * lib/ui_web/controllers/error_html/500.html.heex 10 | # 11 | # embed_templates "error_html/*" 12 | 13 | # The default is to render a plain text page based on 14 | # the template name. For example, "404.html" becomes 15 | # "Not Found". 16 | def render(template, _assigns) do 17 | Phoenix.Controller.status_message_from_template(template) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/controllers/error_json.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.ErrorJSON do 2 | # If you want to customize a particular status code, 3 | # you may add your own clauses, such as: 4 | # 5 | # def render("500.json", _assigns) do 6 | # %{errors: %{detail: "Internal Server Error"}} 7 | # end 8 | 9 | # By default, Phoenix returns the status message from 10 | # the template name. For example, "404.json" becomes 11 | # "Not Found". 12 | def render(template, _assigns) do 13 | %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/controllers/page_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.PageController do 2 | use UiWeb, :controller 3 | 4 | def home(conn, _params) do 5 | # The home page is often custom made, 6 | # so skip the default app layout. 7 | render(conn, :home, layout: false) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/controllers/page_html.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.PageHTML do 2 | use UiWeb, :html 3 | 4 | embed_templates "page_html/*" 5 | end 6 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :ui 3 | 4 | # The session will be stored in the cookie and signed, 5 | # this means its contents can be read but not tampered with. 6 | # Set :encryption_salt if you would also like to encrypt it. 7 | @session_options [ 8 | store: :cookie, 9 | key: "_ui_key", 10 | signing_salt: "hTtiEQ8M", 11 | same_site: "Lax" 12 | ] 13 | 14 | socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] 15 | 16 | # Serve at "/" the static files from "priv/static" directory. 17 | # 18 | # You should set gzip to true if you are running phx.digest 19 | # when deploying your static files in production. 20 | plug Plug.Static, 21 | at: "/", 22 | from: :ui, 23 | gzip: false, 24 | only: UiWeb.static_paths() 25 | 26 | # Code reloading can be explicitly enabled under the 27 | # :code_reloader configuration of your endpoint. 28 | if code_reloading? do 29 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket 30 | plug Phoenix.LiveReloader 31 | plug Phoenix.CodeReloader 32 | plug Phoenix.Ecto.CheckRepoStatus, otp_app: :ui 33 | end 34 | 35 | plug Phoenix.LiveDashboard.RequestLogger, 36 | param_key: "request_logger", 37 | cookie_key: "request_logger" 38 | 39 | plug Plug.RequestId 40 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 41 | 42 | plug Plug.Parsers, 43 | parsers: [:urlencoded, :multipart, :json], 44 | pass: ["*/*"], 45 | json_decoder: Phoenix.json_library() 46 | 47 | plug Plug.MethodOverride 48 | plug Plug.Head 49 | plug Plug.Session, @session_options 50 | plug UiWeb.Router 51 | end 52 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.Gettext do 2 | @moduledoc """ 3 | A module providing Internationalization with a gettext-based API. 4 | 5 | By using [Gettext](https://hexdocs.pm/gettext), 6 | your module gains a set of macros for translations, for example: 7 | 8 | import UiWeb.Gettext 9 | 10 | # Simple translation 11 | gettext("Here is the string to translate") 12 | 13 | # Plural translation 14 | ngettext("Here is the string to translate", 15 | "Here are the strings to translate", 16 | 3) 17 | 18 | # Domain-based translation 19 | dgettext("errors", "Here is the error message to translate") 20 | 21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. 22 | """ 23 | use Gettext, otp_app: :ui 24 | end 25 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/live/page_live.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.PageLive do 2 | use UiWeb, :live_view 3 | 4 | @refresh_interval_ms 1000 5 | 6 | @impl Phoenix.LiveView 7 | def mount(_params, _session, socket) do 8 | socket = 9 | socket 10 | |> assign(current_time_second: System.monotonic_time(:second)) 11 | |> assign(serial_number: get_serial_number()) 12 | 13 | if connected?(socket) do 14 | schedule_refresh() 15 | end 16 | 17 | {:ok, socket, layout: false} 18 | end 19 | 20 | @impl Phoenix.LiveView 21 | def handle_info(:tick, socket) do 22 | schedule_refresh() 23 | socket = assign(socket, current_time_second: System.monotonic_time(:second)) 24 | 25 | {:noreply, socket} 26 | end 27 | 28 | defp schedule_refresh() do 29 | Process.send_after(self(), :tick, @refresh_interval_ms) 30 | end 31 | 32 | defp get_serial_number() do 33 | case Code.ensure_loaded(Nerves.Runtime) do 34 | {:module, _} -> Nerves.Runtime.serial_number() 35 | _error -> "Unavailable" 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/live/user_live/form_component.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.UserLive.FormComponent do 2 | use UiWeb, :live_component 3 | 4 | alias Ui.Accounts 5 | 6 | @impl true 7 | def render(assigns) do 8 | ~H""" 9 |
10 | <.header> 11 | <%= @title %> 12 | <:subtitle>Use this form to manage user records in your database. 13 | 14 | 15 | <.simple_form 16 | for={@form} 17 | id="user-form" 18 | phx-target={@myself} 19 | phx-change="validate" 20 | phx-submit="save" 21 | > 22 | <.input field={@form[:name]} type="text" label="Name" /> 23 | <.input field={@form[:age]} type="number" label="Age" /> 24 | <:actions> 25 | <.button phx-disable-with="Saving...">Save User 26 | 27 | 28 |
29 | """ 30 | end 31 | 32 | @impl true 33 | def update(%{user: user} = assigns, socket) do 34 | {:ok, 35 | socket 36 | |> assign(assigns) 37 | |> assign_new(:form, fn -> 38 | to_form(Accounts.change_user(user)) 39 | end)} 40 | end 41 | 42 | @impl true 43 | def handle_event("validate", %{"user" => user_params}, socket) do 44 | changeset = Accounts.change_user(socket.assigns.user, user_params) 45 | {:noreply, assign(socket, form: to_form(changeset, action: :validate))} 46 | end 47 | 48 | def handle_event("save", %{"user" => user_params}, socket) do 49 | save_user(socket, socket.assigns.action, user_params) 50 | end 51 | 52 | defp save_user(socket, :edit, user_params) do 53 | case Accounts.update_user(socket.assigns.user, user_params) do 54 | {:ok, user} -> 55 | notify_parent({:saved, user}) 56 | 57 | {:noreply, 58 | socket 59 | |> put_flash(:info, "User updated successfully") 60 | |> push_patch(to: socket.assigns.patch)} 61 | 62 | {:error, %Ecto.Changeset{} = changeset} -> 63 | {:noreply, assign(socket, form: to_form(changeset))} 64 | end 65 | end 66 | 67 | defp save_user(socket, :new, user_params) do 68 | case Accounts.create_user(user_params) do 69 | {:ok, user} -> 70 | notify_parent({:saved, user}) 71 | 72 | {:noreply, 73 | socket 74 | |> put_flash(:info, "User created successfully") 75 | |> push_patch(to: socket.assigns.patch)} 76 | 77 | {:error, %Ecto.Changeset{} = changeset} -> 78 | {:noreply, assign(socket, form: to_form(changeset))} 79 | end 80 | end 81 | 82 | defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) 83 | end 84 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/live/user_live/index.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.UserLive.Index do 2 | use UiWeb, :live_view 3 | 4 | alias Ui.Accounts 5 | alias Ui.Accounts.User 6 | 7 | @impl true 8 | def mount(_params, _session, socket) do 9 | {:ok, stream(socket, :users, Accounts.list_users())} 10 | end 11 | 12 | @impl true 13 | def handle_params(params, _url, socket) do 14 | {:noreply, apply_action(socket, socket.assigns.live_action, params)} 15 | end 16 | 17 | defp apply_action(socket, :edit, %{"id" => id}) do 18 | socket 19 | |> assign(:page_title, "Edit User") 20 | |> assign(:user, Accounts.get_user!(id)) 21 | end 22 | 23 | defp apply_action(socket, :new, _params) do 24 | socket 25 | |> assign(:page_title, "New User") 26 | |> assign(:user, %User{}) 27 | end 28 | 29 | defp apply_action(socket, :index, _params) do 30 | socket 31 | |> assign(:page_title, "Listing Users") 32 | |> assign(:user, nil) 33 | end 34 | 35 | @impl true 36 | def handle_info({UiWeb.UserLive.FormComponent, {:saved, user}}, socket) do 37 | {:noreply, stream_insert(socket, :users, user)} 38 | end 39 | 40 | @impl true 41 | def handle_event("delete", %{"id" => id}, socket) do 42 | user = Accounts.get_user!(id) 43 | {:ok, _} = Accounts.delete_user(user) 44 | 45 | {:noreply, stream_delete(socket, :users, user)} 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/live/user_live/index.html.heex: -------------------------------------------------------------------------------- 1 | <.header> 2 | Listing Users 3 | <:actions> 4 | <.link patch={~p"/users/new"}> 5 | <.button>New User 6 | 7 | 8 | 9 | 10 | <.table 11 | id="users" 12 | rows={@streams.users} 13 | row_click={fn {_id, user} -> JS.navigate(~p"/users/#{user}") end} 14 | > 15 | <:col :let={{_id, user}} label="Name"><%= user.name %> 16 | <:col :let={{_id, user}} label="Age"><%= user.age %> 17 | <:action :let={{_id, user}}> 18 |
19 | <.link navigate={~p"/users/#{user}"}>Show 20 |
21 | <.link patch={~p"/users/#{user}/edit"}>Edit 22 | 23 | <:action :let={{id, user}}> 24 | <.link 25 | phx-click={JS.push("delete", value: %{id: user.id}) |> hide("##{id}")} 26 | data-confirm="Are you sure?" 27 | > 28 | Delete 29 | 30 | 31 | 32 | 33 | <.modal :if={@live_action in [:new, :edit]} id="user-modal" show on_cancel={JS.patch(~p"/users")}> 34 | <.live_component 35 | module={UiWeb.UserLive.FormComponent} 36 | id={@user.id || :new} 37 | title={@page_title} 38 | action={@live_action} 39 | user={@user} 40 | patch={~p"/users"} 41 | /> 42 | 43 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/live/user_live/show.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.UserLive.Show do 2 | use UiWeb, :live_view 3 | 4 | alias Ui.Accounts 5 | 6 | @impl true 7 | def mount(_params, _session, socket) do 8 | {:ok, socket} 9 | end 10 | 11 | @impl true 12 | def handle_params(%{"id" => id}, _, socket) do 13 | {:noreply, 14 | socket 15 | |> assign(:page_title, page_title(socket.assigns.live_action)) 16 | |> assign(:user, Accounts.get_user!(id))} 17 | end 18 | 19 | defp page_title(:show), do: "Show User" 20 | defp page_title(:edit), do: "Edit User" 21 | end 22 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/live/user_live/show.html.heex: -------------------------------------------------------------------------------- 1 | <.header> 2 | User <%= @user.id %> 3 | <:subtitle>This is a user record from your database. 4 | <:actions> 5 | <.link patch={~p"/users/#{@user}/show/edit"} phx-click={JS.push_focus()}> 6 | <.button>Edit user 7 | 8 | 9 | 10 | 11 | <.list> 12 | <:item title="Name"><%= @user.name %> 13 | <:item title="Age"><%= @user.age %> 14 | 15 | 16 | <.back navigate={~p"/users"}>Back to users 17 | 18 | <.modal :if={@live_action == :edit} id="user-modal" show on_cancel={JS.patch(~p"/users/#{@user}")}> 19 | <.live_component 20 | module={UiWeb.UserLive.FormComponent} 21 | id={@user.id} 22 | title={@page_title} 23 | action={@live_action} 24 | user={@user} 25 | patch={~p"/users/#{@user}"} 26 | /> 27 | 28 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/lib/ui_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.Router do 2 | use UiWeb, :router 3 | 4 | pipeline :browser do 5 | plug :accepts, ["html"] 6 | plug :fetch_session 7 | plug :fetch_live_flash 8 | plug :put_root_layout, html: {UiWeb.Layouts, :root} 9 | plug :protect_from_forgery 10 | plug :put_secure_browser_headers 11 | end 12 | 13 | pipeline :api do 14 | plug :accepts, ["json"] 15 | end 16 | 17 | scope "/", UiWeb do 18 | pipe_through :browser 19 | 20 | live "/", PageLive, :index 21 | 22 | live "/users", UserLive.Index, :index 23 | live "/users/new", UserLive.Index, :new 24 | live "/users/:id/edit", UserLive.Index, :edit 25 | 26 | live "/users/:id", UserLive.Show, :show 27 | live "/users/:id/show/edit", UserLive.Show, :edit 28 | end 29 | 30 | # Other scopes may use custom stacks. 31 | # scope "/api", UiWeb do 32 | # pipe_through :api 33 | # end 34 | 35 | # Enable LiveDashboard in development 36 | if Application.compile_env(:ui, :dev_routes) do 37 | # If you want to use the LiveDashboard in production, you should put 38 | # it behind authentication and allow only admins to access it. 39 | # If your application does not have an admins-only section yet, 40 | # you can use Plug.BasicAuth to set up some basic authentication 41 | # as long as you are also using SSL (which you should anyway). 42 | import Phoenix.LiveDashboard.Router 43 | 44 | scope "/dev" do 45 | pipe_through :browser 46 | 47 | live_dashboard "/dashboard", metrics: UiWeb.Telemetry 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Ui.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :ui, 7 | version: "0.1.0", 8 | elixir: "~> 1.16", 9 | elixirc_paths: elixirc_paths(Mix.env()), 10 | start_permanent: Mix.env() == :prod, 11 | aliases: aliases(), 12 | deps: deps() 13 | ] 14 | end 15 | 16 | # Configuration for the OTP application. 17 | # 18 | # Type `mix help compile.app` for more information. 19 | def application do 20 | [ 21 | mod: {Ui.Application, []}, 22 | extra_applications: [:logger, :runtime_tools] 23 | ] 24 | end 25 | 26 | # Specifies which paths to compile per environment. 27 | defp elixirc_paths(:test), do: ["lib", "test/support"] 28 | defp elixirc_paths(_), do: ["lib"] 29 | 30 | # Specifies your project dependencies. 31 | # 32 | # Type `mix help deps` for examples and options. 33 | defp deps do 34 | [ 35 | {:phoenix, "~> 1.7.10"}, 36 | {:phoenix_ecto, "~> 4.4"}, 37 | {:ecto_sql, "~> 3.10"}, 38 | {:ecto_sqlite3, ">= 0.0.0"}, 39 | {:phoenix_html, "~> 3.3"}, 40 | {:phoenix_live_reload, "~> 1.2", only: :dev, targets: :host}, 41 | {:phoenix_live_view, "~> 0.20.1"}, 42 | {:floki, ">= 0.30.0", only: :test}, 43 | {:phoenix_live_dashboard, "~> 0.8.2"}, 44 | {:esbuild, "~> 0.8", runtime: Mix.env() == :dev && Mix.target() == :host}, 45 | {:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev && Mix.target() == :host}, 46 | {:heroicons, 47 | github: "tailwindlabs/heroicons", 48 | tag: "v2.1.1", 49 | sparse: "optimized", 50 | app: false, 51 | compile: false, 52 | depth: 1}, 53 | {:telemetry_metrics, "~> 0.6"}, 54 | {:telemetry_poller, "~> 1.0"}, 55 | {:gettext, "~> 0.20"}, 56 | {:jason, "~> 1.2"}, 57 | {:dns_cluster, "~> 0.1.1"}, 58 | {:plug_cowboy, "~> 2.5"} 59 | ] 60 | end 61 | 62 | # Aliases are shortcuts or tasks specific to the current project. 63 | # For example, to install project dependencies and perform other setup tasks, run: 64 | # 65 | # $ mix setup 66 | # 67 | # See the documentation for `Mix` for more info on aliases. 68 | defp aliases do 69 | [ 70 | setup: ["deps.get", "ecto.setup", "assets.setup", "assets.build"], 71 | "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 72 | "ecto.reset": ["ecto.drop", "ecto.setup"], 73 | test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"], 74 | "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"], 75 | "assets.build": ["tailwind default", "esbuild default"], 76 | "assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"] 77 | ] 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/priv/repo/migrations/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto_sql], 3 | inputs: ["*.exs"] 4 | ] 5 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/priv/repo/migrations/20211206175437_create_users.exs: -------------------------------------------------------------------------------- 1 | defmodule Ui.Repo.Migrations.CreateUsers do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:users) do 6 | add :name, :string 7 | add :age, :integer 8 | 9 | timestamps() 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/priv/repo/seeds.exs: -------------------------------------------------------------------------------- 1 | # Script for populating the database. You can run it as: 2 | # 3 | # mix run priv/repo/seeds.exs 4 | # 5 | # Inside the script, you can read and write to any of your 6 | # repositories directly: 7 | # 8 | # Ui.Repo.insert!(%Ui.SomeSchema{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/priv/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerves-project/nerves_examples/d7a4726b59e422cf4052775de9bb33c81a4a5b58/poncho_phoenix/ui/priv/static/favicon.ico -------------------------------------------------------------------------------- /poncho_phoenix/ui/priv/static/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | Such tests rely on `Phoenix.ConnTest` and also 7 | import other functionality to make it easier 8 | to build common data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | we enable the SQL sandbox, so changes done to the database 12 | are reverted at the end of every test. If you are using 13 | PostgreSQL, you can even run database tests asynchronously 14 | by setting `use UiWeb.ConnCase, async: true`, although 15 | this option is not recommended for other databases. 16 | """ 17 | 18 | use ExUnit.CaseTemplate 19 | 20 | using do 21 | quote do 22 | # The default endpoint for testing 23 | @endpoint UiWeb.Endpoint 24 | 25 | use UiWeb, :verified_routes 26 | 27 | # Import conveniences for testing with connections 28 | import Plug.Conn 29 | import Phoenix.ConnTest 30 | import UiWeb.ConnCase 31 | end 32 | end 33 | 34 | setup tags do 35 | Ui.DataCase.setup_sandbox(tags) 36 | {:ok, conn: Phoenix.ConnTest.build_conn()} 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/test/support/data_case.ex: -------------------------------------------------------------------------------- 1 | defmodule Ui.DataCase do 2 | @moduledoc """ 3 | This module defines the setup for tests requiring 4 | access to the application's data layer. 5 | 6 | You may define functions here to be used as helpers in 7 | your tests. 8 | 9 | Finally, if the test case interacts with the database, 10 | we enable the SQL sandbox, so changes done to the database 11 | are reverted at the end of every test. If you are using 12 | PostgreSQL, you can even run database tests asynchronously 13 | by setting `use Ui.DataCase, async: true`, although 14 | this option is not recommended for other databases. 15 | """ 16 | 17 | use ExUnit.CaseTemplate 18 | 19 | using do 20 | quote do 21 | alias Ui.Repo 22 | 23 | import Ecto 24 | import Ecto.Changeset 25 | import Ecto.Query 26 | import Ui.DataCase 27 | end 28 | end 29 | 30 | setup tags do 31 | Ui.DataCase.setup_sandbox(tags) 32 | :ok 33 | end 34 | 35 | @doc """ 36 | Sets up the sandbox based on the test tags. 37 | """ 38 | def setup_sandbox(tags) do 39 | pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Ui.Repo, shared: not tags[:async]) 40 | on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) 41 | end 42 | 43 | @doc """ 44 | A helper that transforms changeset errors into a map of messages. 45 | 46 | assert {:error, changeset} = Accounts.create_user(%{password: "short"}) 47 | assert "password is too short" in errors_on(changeset).password 48 | assert %{password: ["password is too short"]} = errors_on(changeset) 49 | 50 | """ 51 | def errors_on(changeset) do 52 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> 53 | Regex.replace(~r"%{(\w+)}", message, fn _, key -> 54 | opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() 55 | end) 56 | end) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | Ecto.Adapters.SQL.Sandbox.mode(Ui.Repo, :manual) 3 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/test/ui_web/controllers/error_html_test.exs: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.ErrorHTMLTest do 2 | use UiWeb.ConnCase, async: true 3 | 4 | # Bring render_to_string/4 for testing custom views 5 | import Phoenix.Template 6 | 7 | test "renders 404.html" do 8 | assert render_to_string(UiWeb.ErrorHTML, "404", "html", []) == "Not Found" 9 | end 10 | 11 | test "renders 500.html" do 12 | assert render_to_string(UiWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/test/ui_web/controllers/error_json_test.exs: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.ErrorJSONTest do 2 | use UiWeb.ConnCase, async: true 3 | 4 | test "renders 404" do 5 | assert UiWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}} 6 | end 7 | 8 | test "renders 500" do 9 | assert UiWeb.ErrorJSON.render("500.json", %{}) == 10 | %{errors: %{detail: "Internal Server Error"}} 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /poncho_phoenix/ui/test/ui_web/controllers/page_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule UiWeb.PageControllerTest do 2 | use UiWeb.ConnCase 3 | 4 | test "GET /", %{conn: conn} do 5 | conn = get(conn, ~p"/") 6 | assert html_response(conn, 200) =~ "Peace of mind from prototype to production" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /scripts/build-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ "$MIX_TARGET" = "" ]; then 6 | echo "Set MIX_TARGET to the desired target" 7 | echo "For example:" 8 | echo 9 | echo "export MIX_TARGET=rpi0" 10 | echo "./build-all.sh" 11 | exit 1 12 | fi 13 | 14 | . scripts/projects.sh 15 | 16 | build() { 17 | if [ -e ".skip-$MIX_TARGET" ]; then 18 | return 19 | fi 20 | 21 | # Retry fetching dependencies since sometimes the network is flaky on CI 22 | n=0 23 | until [ $n -ge 5 ]; do 24 | mix deps.get && break 25 | n=$((n+1)) 26 | echo "Error while fetching deps. Retrying in 5 seconds" 27 | sleep 5 28 | done 29 | 30 | if [ -e ".requires-zig" ]; then 31 | MIX_TARGET=host mix zig.get 32 | fi 33 | 34 | mix firmware 35 | } 36 | 37 | for project in $PROJECTS; do 38 | echo "=== $project ===" 39 | (cd $project && build) 40 | done 41 | 42 | echo "Success" 43 | 44 | -------------------------------------------------------------------------------- /scripts/clean-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source scripts/projects.sh 6 | 7 | clean() { 8 | echo "Cleaning $1..." 9 | (cd $1 && rm -fr deps _build) 10 | } 11 | 12 | for project in $PROJECTS; do 13 | clean $project 14 | done 15 | 16 | echo "Success" 17 | 18 | -------------------------------------------------------------------------------- /scripts/format-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source scripts/projects.sh 6 | 7 | format() { 8 | echo "Formatting $1..." 9 | (cd $1 && mix format) 10 | } 11 | 12 | for project in $ELIXIR_PROJECTS; do 13 | format $project 14 | done 15 | 16 | echo "Success" 17 | 18 | -------------------------------------------------------------------------------- /scripts/projects.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TARGETS="rpi0 rpi rpi2 rpi3 rpi3a rpi4 bbb x86_64 osd32mp1 mangopi_mq_pro" 4 | ELIXIR_PROJECTS="blinky \ 5 | hello_gpio \ 6 | minimal \ 7 | poncho_phoenix/firmware \ 8 | hello_live_view \ 9 | hello_wifi \ 10 | hello_sqlite \ 11 | hello_snmp_manager \ 12 | hello_snmp_agent \ 13 | hello_scenic" 14 | PROJECTS="$ELIXIR_PROJECTS hello_erlang hello_lfe" 15 | -------------------------------------------------------------------------------- /scripts/update-deps-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source scripts/projects.sh 6 | 7 | update_deps() { 8 | echo "Updating deps for $1..." 9 | (cd $1 && MIX_TARGET=rpi0 mix deps.update --all) 10 | } 11 | 12 | # Clean everything up 13 | echo "Cleaning project directory..." 14 | git clean -fdx 15 | find . -name deps | xargs rm -fr 16 | find . -name _build | xargs rm -fr 17 | find . -name mix.lock -delete 18 | 19 | # Now update all deps 20 | for project in $PROJECTS; do 21 | update_deps $project 22 | done 23 | update_deps poncho_phoenix/ui 24 | 25 | echo "Success" 26 | 27 | --------------------------------------------------------------------------------