defmodule Nrf24 do @moduledoc """ Library for trasmitting and receiveing data with nRF24L01+ transciever. It works, by default, in ShockBurst mode with ACK (auto acknowledgement) and CRC check enabled. However it can be configured to disable both. """ use GenServer require Logger alias Nrf24.Transciever # Client @type init_options() :: [ name: GenServer.name(), ce_pin: integer(), csn_pin: integer(), channel: integer(), crc_length: 0 | 1 | 2, speed: :low | :medium | :hi, pipes: [ [ pipe_no: integer(), address: integer(), payload_size: integer(), auto_ack: boolean() ] ] ] @doc """ nRF24L01+ GenServer start_link options * `:name` - GenServer nome * `:bus_name` - SPI bus name (e.g. "spidev0.0", "spidev1.0", default: "spidev0.0") * `:ce_pin` - Rasbperry PI pin to which transciever's CE pin is connected * `:csn_pin` - Rasbperry PI pin to which transciever's CSN pin is connected * `:channel` - Frequency channel on which transciever will operate (default: 0x4c) * `:crc_length` - CRC length for transmitted data verification (values: 1, 2, default: 2) * `:speed` - Data transfer speed (values: low, med, high, default: med) Data speed values: * `:low` - 250Kbp * `:medium` - 1Mbps * `:high` - 2Mbps Pipe configuration options: * `:pipe_no` - Pipe number (values 0 to 5) * `:address` - For pipes 0 and 1 5-byte address and for other single byte address * `:payload_size` - Size of data that will be received through the pipe * `:auto_ack` - Turning auto-acknowledge on or off for the pipe (default: true) If pipes configuration is missing, default, factory set, valuse will be used. """ @spec start_link(init_options()) :: GenServer.on_start() def start_link(args) do options = Keyword.take(args, [:name]) GenServer.start_link(__MODULE__, args, options) end # Server @impl GenServer def init(args) do bus_name = Keyword.get(args, :bus_name, "spidev0.0") case Circuits.SPI.open(bus_name) do {:error, error} -> {:stop, error} {:ok, spi} -> ce_pin = Keyword.get(args, :ce_pin) csn_pin = Keyword.get(args, :csn_pin) channel = Keyword.get(args, :channel, 0x4C) crc_length = Keyword.get(args, :crc_length, 2) speed = Keyword.get(args, :speed, :medium) with {:ok, _} <- Transciever.set_channel(spi, channel), {:ok, _} <- Transciever.set_crc_length(spi, crc_length), {:ok, _} <- Transciever.set_speed(spi, speed) do Keyword.get(args, :pipes, []) |> Enum.each(fn pipe_opts -> pipe_no = Keyword.get(pipe_opts, :pipe_no) Transciever.set_pipe(spi, pipe_no, pipe_opts) end) state = %{ spi: spi, ce_pin: ce_pin, csn_pin: csn_pin } {:ok, state} else {:error, error} -> {:stop, error} end end end @impl GenServer def handle_call({:set_channel, channel}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_channel(spi, channel), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:set_crc_length, length}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_crc_length(spi, length), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call(:set_receive_mode, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_receive_mode(spi), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call(:set_transmit_mode, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_transmit_mode(spi), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call(:power_on, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.power_on(spi), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call(:power_off, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.power_off(spi), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:set_speed, speed}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_speed(spi, speed), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:set_pipe_address, pipe_no, address}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_pipe_address(spi, pipe_no, address), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:set_trasmit_address, address}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_transmit_address(spi, address), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:set_payload_size, pipe_no, payload_size}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_payload_size(spi, pipe_no, payload_size), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:ack_on, pipe_no}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.ack(spi, pipe_no, true), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:ack_off, pipe_no}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.ack(spi, pipe_no, false), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:enable_pipe, pipe_no}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.enable_pipe(spi, pipe_no), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:disable_pipe, pipe_no}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.disable_pipe(spi, pipe_no), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:set_retransmit_delay, delay}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_retransmit_delay(spi, delay), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:set_retransmit_count, count}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_retransmit_count(spi, count), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call(:reset_status, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.reset_status(spi), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:set_pipe, pipe_no, options}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.set_pipe(spi, pipe_no, options), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call(:start_listening, _from, state = %{spi: spi, ce_pin: ce_pin}) do if is_reference(spi) do {:reply, Transciever.start_listening(spi, ce_pin), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call(:stop_listening, _from, state = %{spi: spi, ce_pin: ce_pin}) do if is_reference(spi) do {:reply, Transciever.stop_listening(spi, ce_pin), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call(:reset_device, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.reset(spi), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:read_data, payload_size}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.read_data(spi, payload_size), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:write_register, reg, value}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.write_register(spi, reg, value), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:read_register, reg}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.read_register(spi, reg), state} else {:reply, {:error, :spi_not_opened}} end end def handle_call({:read_register, reg, bytes_no}, _from, state = %{spi: spi}) do if is_reference(spi) do {:reply, Transciever.read_register(spi, reg, bytes_no), state} else {:reply, {:error, :spi_not_opened}} end end @impl GenServer def handle_cast({:send, address, data}, state = %{spi: spi, csn_pin: csn_pin, ce_pin: ce_pin}) do with :ok <- Transciever.start_sending(spi, address), {:ok, ce} <- Transciever.send_data(spi, data, csn_pin, ce_pin) do Process.send_after(self(), {:stop_sending, ce}, 1) else {:error, error} -> Logger.info("Send data failed with error: #{inspect(error)}") _ -> Logger.info("Send data failed with unknown error") end {:noreply, state} end @impl GenServer def handle_info({:stop_sending, ce}, state) do Transciever.stop_sending(ce) {:noreply, state} end @impl GenServer def terminate(_reason, %{spi: spi}) do if is_reference(spi), do: Circuits.SPI.close(spi) end end