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 b5a063a..f33f6ff 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,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 @@ -61,6 +62,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 6d0eb98..43113ae 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -17,7 +17,7 @@ SPDX-FileCopyrightText = "None" SPDX-License-Identifier = "CC0-1.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 f5e114d..6c705f8 100644 --- a/mix.exs +++ b/mix.exs @@ -7,47 +7,54 @@ defmodule Circuits.I2C.MixProject do @source_url "https://github.com/elixir-circuits/#{@app}" def project do - [ + base = [ app: @app, 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", @@ -86,24 +93,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 @@ -124,12 +150,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 @@ -138,7 +160,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