diff --git a/.formatter.exs b/.formatter.exs index 5a0d2db..b8a2b06 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,4 +1,4 @@ # Used by "mix format" [ - inputs: ["{mix,.formatter,.credo}.exs", "{config,lib,test}/**/*.{ex,exs}"] + inputs: ["{mix,.formatter,.credo}.exs", "{config,i2c_dev,lib,test}/**/*.{ex,exs}"] ] diff --git a/Makefile b/Makefile index 0e3768b..c524625 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ # Variables to override: # # MIX_APP_PATH path to the build directory -# CIRCUITS_I2C_I2CDEV Backend to build - `"normal"`, `"test"`, or `"disabled"` will build a NIF +# CIRCUITS_I2C_I2CDEV Backend to build - `"normal"` or `"test"` # # CC C compiler # CROSSCOMPILE crosscompiler prefix, if any @@ -30,7 +30,7 @@ BUILD = $(MIX_APP_PATH)/obj NIF = $(PREFIX)/i2c_nif.so -SRC = c_src/i2c_nif.c +SRC = i2c_dev/c_src/i2c_nif.c CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter -pedantic # Check that we're on a supported build platform @@ -40,7 +40,7 @@ ifeq ($(shell uname -s),Linux) CFLAGS += -fPIC LDFLAGS += -fPIC -shared else -CFLAGS += -Ic_src/compat +CFLAGS += -Ii2c_dev/c_src/compat LDFLAGS += -undefined dynamic_lookup -dynamiclib ifeq ($(CIRCUITS_I2C_I2CDEV),normal) $(error Circuits.I2C Linux I2CDev backend is not supported on non-Linux platforms. Review circuits_i2c backend configuration or report an issue if improperly detected.) @@ -59,8 +59,7 @@ ifeq ($(CIRCUITS_I2C_I2CDEV),test) # Stub out ioctls and send back test data CFLAGS += -DTEST_BACKEND else -# Don't build NIF -NIF = +$(error Invalid CIRCUITS_I2C_I2CDEV value: $(CIRCUITS_I2C_I2CDEV)) endif endif @@ -68,8 +67,8 @@ endif ERL_CFLAGS ?= -I"$(ERL_EI_INCLUDE_DIR)" ERL_LDFLAGS ?= -L"$(ERL_EI_LIBDIR)" -lei -HEADERS =$(wildcard c_src/*.h) -OBJ = $(SRC:c_src/%.c=$(BUILD)/%.o) +HEADERS =$(wildcard i2c_dev/c_src/*.h) +OBJ = $(SRC:i2c_dev/c_src/%.c=$(BUILD)/%.o) calling_from_make: mix compile @@ -80,7 +79,7 @@ install: $(PREFIX) $(BUILD) $(NIF) $(OBJ): $(HEADERS) Makefile -$(BUILD)/%.o: c_src/%.c +$(BUILD)/%.o: i2c_dev/c_src/%.c @echo " CC $(notdir $@)" $(CC) -c $(ERL_CFLAGS) $(CFLAGS) -o $@ $< diff --git a/README.md b/README.md index e3d751e..d634158 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,8 @@ may be helpful. Internally, it uses the [Linux "i2c-dev" interface](https://elixir.bootlin.com/linux/latest/source/Documentation/i2c/dev-interface) -so that it does not require board-dependent code. +so that it does not require board-dependent code. See the application +configuration for disabling all things Linux/Nerves specific. ## Getting started without hardware @@ -56,6 +57,24 @@ If you don't have any real I2C devices, it's possible to work with simulated devices. See the [CircuitsSim](https://github.com/elixir-circuits/circuits_sim) project for details. +## Application config + +`Circuits.I2C` supports the following application environment keys: + +| Key | Description | +| --- | ----------- | +| `:include_i2c_dev` | Set to `true` or `false` to indicate whether or not to include the `Circuits.I2C.I2CDev` backend. | +| `:default_backend` | Default I2C backend to use when unspecified in API calls. The format is either a module name or a `{module_name, options}` tuple. | + +`Circuits.I2C` uses a heuristic for the default values of both +`:include_i2c_dev`. For example, if the `:default_backend` is set to +`Circuits.I2C.I2CDev`, then `:include_i2c_dev` has to be `true`. If +`:default_backend` is set to something else, then `:include_i2c_dev` defaults to +`false`. If neither is set, then `Circuits.I2C.I2CDev` is built. On non-Linux +platforms, the `Circuits.I2C.I2CDev` NIF will be compiled in test mode which +minimally supports unit testing. Mocking is generally a better option for most +users, though. + ## I2C background An [Inter-Integrated Circuit](https://en.wikipedia.org/wiki/I%C2%B2C) (I2C) bus diff --git a/REUSE.toml b/REUSE.toml index 58c7c34..e160407 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -28,7 +28,7 @@ SPDX-FileCopyrightText = "2014 Frank Hunleth" SPDX-License-Identifier = "CC-BY-4.0" [[annotations]] -path = ["c_src/linux/i2c-dev.h"] +path = ["i2c_dev/c_src/linux/i2c-dev.h"] precedence = "aggregate" SPDX-FileCopyrightText = "None" SPDX-License-Identifier = "GPL-2.0-or-later WITH Linux-syscall-note" diff --git a/c_src/compat/linux/types.h b/i2c_dev/c_src/compat/linux/types.h similarity index 100% rename from c_src/compat/linux/types.h rename to i2c_dev/c_src/compat/linux/types.h diff --git a/c_src/i2c_nif.c b/i2c_dev/c_src/i2c_nif.c similarity index 100% rename from c_src/i2c_nif.c rename to i2c_dev/c_src/i2c_nif.c diff --git a/c_src/linux/i2c-dev.h b/i2c_dev/c_src/linux/i2c-dev.h similarity index 100% rename from c_src/linux/i2c-dev.h rename to i2c_dev/c_src/linux/i2c-dev.h diff --git a/lib/i2c/i2c_dev.ex b/i2c_dev/lib/i2c/i2c_dev.ex similarity index 100% rename from lib/i2c/i2c_dev.ex rename to i2c_dev/lib/i2c/i2c_dev.ex diff --git a/lib/i2c/i2c_nif.ex b/i2c_dev/lib/i2c/i2c_nif.ex similarity index 100% rename from lib/i2c/i2c_nif.ex rename to i2c_dev/lib/i2c/i2c_nif.ex diff --git a/lib/i2c/nil_backend.ex b/lib/i2c/nil_backend.ex index 528eb8d..dd0562f 100644 --- a/lib/i2c/nil_backend.ex +++ b/lib/i2c/nil_backend.ex @@ -9,6 +9,9 @@ defmodule Circuits.I2C.NilBackend do @behaviour Circuits.I2C.Backend alias Circuits.I2C.Backend + alias Circuits.I2C.Bus + + defstruct [] @doc """ Return the I2C bus names on this system @@ -35,4 +38,27 @@ defmodule Circuits.I2C.NilBackend do def info() do %{name: __MODULE__} end + + defimpl Bus do + @impl Bus + def flags(%Circuits.I2C.NilBackend{}), do: [] + + @impl Bus + def read(%Circuits.I2C.NilBackend{}, _address, _count, _options) do + {:error, :unimplemented} + end + + @impl Bus + def write(%Circuits.I2C.NilBackend{}, _address, _data, _options) do + {:error, :unimplemented} + end + + @impl Bus + def write_read(%Circuits.I2C.NilBackend{}, _address, _write_data, _read_count, _options) do + {:error, :unimplemented} + end + + @impl Bus + def close(%Circuits.I2C.NilBackend{}), do: :ok + end end diff --git a/mix.exs b/mix.exs index 658b0e3..098b75c 100644 --- a/mix.exs +++ b/mix.exs @@ -6,47 +6,54 @@ defmodule Circuits.I2C.MixProject do @source_url "https://github.com/elixir-circuits/circuits_i2c" def project do - [ + base = [ app: :circuits_i2c, version: @version, elixir: "~> 1.13", description: @description, package: package(), source_url: @source_url, - compilers: [:elixir_make | Mix.compilers()], - elixirc_paths: elixirc_paths(Mix.env()), - make_targets: ["all"], - make_clean: ["clean"], docs: docs(), - aliases: [compile: [&set_make_env/1, "compile"], format: [&format_c/1, "format"]], start_permanent: Mix.env() == :prod, dialyzer: [ flags: [:missing_return, :extra_return, :unmatched_returns, :error_handling, :underspecs] ], deps: deps() ] + + if build_i2c_dev?() do + additions = [ + compilers: [:elixir_make | Mix.compilers()], + elixirc_paths: ["lib", "i2c_dev/lib"], + make_targets: ["all"], + make_clean: ["clean"], + aliases: [compile: [&set_make_env/1, "compile"], format: [&format_c/1, "format"]] + ] + + Keyword.merge(base, additions) + else + base + end end def cli do [preferred_envs: %{docs: :docs, "hex.publish": :docs, "hex.build": :docs}] end - defp elixirc_paths(env) when env in [:test, :dev], do: ["lib", "examples"] - defp elixirc_paths(_env), do: ["lib"] - def application do - # IMPORTANT: This provides a default at runtime and at compile-time when + # IMPORTANT: This provides defaults at runtime and at compile-time when # circuits_i2c is pulled in as a dependency. - [env: [default_backend: default_backend()]] + [env: [default_backend: default_backend(), build_i2c_dev: false]] end defp package do %{ files: [ "CHANGELOG.md", - "c_src/*.[ch]", - "c_src/linux/*.h", - "c_src/compat/linux/*.h", + "i2c_dev/c_src/*.[ch]", + "i2c_dev/c_src/linux/*.h", + "i2c_dev/c_src/compat/linux/*.h", + "i2c_dev/lib", "lib", "LICENSES", "Makefile", @@ -85,24 +92,43 @@ defmodule Circuits.I2C.MixProject do ] end - defp default_backend(), do: default_backend(Mix.env(), Mix.target()) - defp default_backend(:test, _target), do: {Circuits.I2C.I2CDev, test: true} + defp build_i2c_dev?() do + include_i2c_dev = Application.get_env(:circuits_i2c, :include_i2c_dev) + + if include_i2c_dev != nil do + # If the user set :include_i2c_dev, then use it + include_i2c_dev + else + # Otherwise, infer whether to build it based on the default_backend + # setting. If default_backend references it, then build it. If it + # references something else, then don't build. Default is to build. + default_backend = Application.get_env(:circuits_i2c, :default_backend) - defp default_backend(_env, :host) do + default_backend == nil or default_backend == Circuits.I2C.I2CDev or + (is_tuple(default_backend) and elem(default_backend, 0) == Circuits.I2C.I2CDev) + end + end + + defp default_backend(), do: default_backend(Mix.env(), Mix.target(), build_i2c_dev?()) + defp default_backend(:test, _target, true), do: {Circuits.I2C.I2CDev, test: true} + + defp default_backend(_env, :host, true) do case :os.type() do {:unix, :linux} -> Circuits.I2C.I2CDev _ -> {Circuits.I2C.I2CDev, test: true} end end + defp default_backend(_env, _target, false), do: Circuits.I2C.NilBackend + # MIX_TARGET set to something besides host - defp default_backend(env, _not_host) do + defp default_backend(env, _not_host, true) do # If CROSSCOMPILE is set, then the Makefile will use the crosscompiler and # assume a Linux/Nerves build If not, then the NIF will be build for the # host, so use the default host backend case System.fetch_env("CROSSCOMPILE") do {:ok, _} -> Circuits.I2C.I2CDev - :error -> default_backend(env, :host) + :error -> default_backend(env, :host, true) end end @@ -123,12 +149,8 @@ defmodule Circuits.I2C.MixProject do end end - defp i2c_dev_compile_mode(Circuits.I2C.I2CDev) do - "normal" - end - defp i2c_dev_compile_mode(_other) do - "disabled" + "normal" end defp format_c([]) do @@ -137,7 +159,7 @@ defmodule Circuits.I2C.MixProject do Mix.Shell.IO.info("Install astyle to format C code.") astyle -> - System.cmd(astyle, ["-n", "c_src/*.c"], into: IO.stream(:stdio, :line)) + System.cmd(astyle, ["-n", "i2c_dev/c_src/*.c"], into: IO.stream(:stdio, :line)) end end diff --git a/mix.lock b/mix.lock index 2f28791..feb5938 100644 --- a/mix.lock +++ b/mix.lock @@ -2,7 +2,7 @@ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, "credo_binary_patterns": {:hex, :credo_binary_patterns, "0.2.6", "cfcaca0bc5c6447b96c5a03eff175c28f86c486be8e95d55b360fb90c2dd18bd", [:mix], [{:credo, "~> 1.6", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm", "d36a2b56ad72bdf3183ccc81d7e7821e78c97de7c127bc8dd99a5f05ca702187"}, - "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, + "dialyxir": {:hex, :dialyxir, "1.4.6", "7cca478334bf8307e968664343cbdb432ee95b4b68a9cba95bdabb0ad5bdfd9a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "8cf5615c5cd4c2da6c501faae642839c8405b49f8aa057ad4ae401cb808ef64d"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},