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. ## Examples # Receiving data {:ok, nrf} = Nrf24.start_link( bus_name: "spidev0.0", ce_pin: 17, csn_pin: 0, channel: 0x4C, crc_length: 2, speed: :medium, pipes: [[pipe_no: 0, address: "1Node", payload_size: 4, auto_ack: true]] ) Nrf24.start_listening(nrf) Nrf24.receive(nrf, 4) Nrf24.stop_listening(nrf) # Sending data {:ok, nrf} = Nrf24.start_link( bus_name: "spidev0.0", ce_pin: 17, csn_pin: 0, channel: 0x4C, crc_length: 2, speed: :medium, ) data = <<9273.69::float-little-size(32)>> Nrf24.send(nrf, "2Node", data) """ use GenServer require Logger alias Nrf24.Transciever # Client @type init_options() :: [ name: GenServer.name(), bus_name: String.t(), 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) * `:pipes` - Data pipes configuration to be used 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, values will be used. ## Example {:ok, nrf} = Nrf24.start_link( bus_name: "spidev0.0", ce_pin: 17, csn_pin: 0, channel: 0x4C, crc_length: 2, speed: :medium, pipes: [[pipe_no: 0, address: "1Node", payload_size: 4, auto_ack: true]] ) """ @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 def set_channel(pid, channel) do GenServer.call(pid, {:set_channel, channel}) end def set_crc_length(pid, length) do GenServer.call(pid, {:set_crc_length, length}) end def set_receive_mode(pid) do GenServer.call(pid, :set_receive_mode) end def set_transmit_mode(pid) do GenServer.call(pid, :set_transmit_mode) end def power_on(pid) do GenServer.call(pid, :power_on) end def power_off(pid) do GenServer.call(pid, :power_off) end def set_speed(pid, speed) do GenServer.call(pid, {:set_speed, speed}) end def set_pipe_address(pid, pipe_no, address) do GenServer.call(pid, {:set_pipe_address, pipe_no, address}) end def set_transmit_address(spi, address) do GenServer.call(spi, {:set_transmit_address, address}) end def set_payload_size(spi, pipe_no, payload_size) do GenServer.call(spi, {:set_payload_size, pipe_no, payload_size}) end def ack_on(spi, pipe_no) do GenServer.call(spi, {:ack_on, pipe_no}) end def ack_off(spi, pipe_no) do GenServer.call(spi, {:ack_off, pipe_no}) end def enable_pipe(spi, pipe_no) do GenServer.call(spi, {:enable_pipe, pipe_no}) end def disable_pipe(spi, pipe_no) do GenServer.call(spi, {:disable_pipe, pipe_no}) end def set_retransmit_delay(spi, delay) do GenServer.call(spi, {:set_retransmit_delay, delay}) end def set_retransmit_count(spi, count) do GenServer.call(spi, {:set_retransmit_count, count}) end def reset_status(spi) do GenServer.call(spi, :reset_status) end def set_pipe(spi, pipe_no, options) do GenServer.call(spi, {:set_pipe, pipe_no, options}) end def start_listening(spi) do GenServer.call(spi, :start_listening) end def stop_listening(spi) do GenServer.call(spi, :stop_listening) end def reset_device(spi) do GenServer.call(spi, :reset_device) end def receive(spi, payload_size, timeout_in_seconds) do GenServer.call(spi, {:receive, payload_size, timeout_in_seconds}, timeout_in_seconds * 1000) end def write_register(spi, register, value) do GenServer.call(spi, {:write_register, register, value}) end def read_register(spi, register) do GenServer.call(spi, {:read_register, register}) end def read_register(spi, register, bytes_no) do GenServer.call(spi, {:read_register, register, bytes_no}) end def send(spi, address, data) do GenServer.cast(spi, {:send, address, data}) 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 # Client API @impl GenServer def handle_call({:set_channel, channel}, _from, state = %{spi: spi}) do {:reply, Transciever.set_channel(spi, channel), state} end def handle_call({:set_crc_length, length}, _from, state = %{spi: spi}) do {:reply, Transciever.set_crc_length(spi, length), state} end def handle_call(:set_receive_mode, _from, state = %{spi: spi}) do {:reply, Transciever.set_receive_mode(spi), state} end def handle_call(:set_transmit_mode, _from, state = %{spi: spi}) do {:reply, Transciever.set_transmit_mode(spi), state} end def handle_call(:power_on, _from, state = %{spi: spi}) do {:reply, Transciever.power_on(spi), state} end def handle_call(:power_off, _from, state = %{spi: spi}) do {:reply, Transciever.power_off(spi), state} end def handle_call({:set_speed, speed}, _from, state = %{spi: spi}) do {:reply, Transciever.set_speed(spi, speed), state} end def handle_call({:set_pipe_address, pipe_no, address}, _from, state = %{spi: spi}) do {:reply, Transciever.set_pipe_address(spi, pipe_no, address), state} end def handle_call({:set_trasmit_address, address}, _from, state = %{spi: spi}) do {:reply, Transciever.set_transmit_address(spi, address), state} end def handle_call({:set_payload_size, pipe_no, payload_size}, _from, state = %{spi: spi}) do {:reply, Transciever.set_payload_size(spi, pipe_no, payload_size), state} end def handle_call({:ack_on, pipe_no}, _from, state = %{spi: spi}) do {:reply, Transciever.ack(spi, pipe_no, true), state} end def handle_call({:ack_off, pipe_no}, _from, state = %{spi: spi}) do {:reply, Transciever.ack(spi, pipe_no, false), state} end def handle_call({:enable_pipe, pipe_no}, _from, state = %{spi: spi}) do {:reply, Transciever.enable_pipe(spi, pipe_no), state} end def handle_call({:disable_pipe, pipe_no}, _from, state = %{spi: spi}) do {:reply, Transciever.disable_pipe(spi, pipe_no), state} end def handle_call({:set_retransmit_delay, delay}, _from, state = %{spi: spi}) do {:reply, Transciever.set_retransmit_delay(spi, delay), state} end def handle_call({:set_retransmit_count, count}, _from, state = %{spi: spi}) do {:reply, Transciever.set_retransmit_count(spi, count), state} end def handle_call(:reset_status, _from, state = %{spi: spi}) do {:reply, Transciever.reset_status(spi), state} end def handle_call({:set_pipe, pipe_no, options}, _from, state = %{spi: spi}) do {:reply, Transciever.set_pipe(spi, pipe_no, options), state} end def handle_call(:start_listening, _from, state = %{spi: spi, ce_pin: ce_pin}) do {:reply, Transciever.start_listening(spi, ce_pin), state} end def handle_call(:stop_listening, _from, state = %{spi: spi, ce_pin: ce_pin}) do {:reply, Transciever.stop_listening(spi, ce_pin), state} end def handle_call(:reset_device, _from, state = %{spi: spi}) do {:reply, Transciever.reset(spi), state} end def handle_call({:receive, payload_size, timeout_in_seconds}, _from, state = %{spi: spi}) do res = wait_for_data(spi, payload_size, timeout_in_seconds) {:reply, res, state} end def handle_call({:write_register, reg, value}, _from, state = %{spi: spi}) do {:reply, Transciever.write_register(spi, reg, value), state} end def handle_call({:read_register, reg}, _from, state = %{spi: spi}) do {:reply, Transciever.read_register(spi, reg), state} end def handle_call({:read_register, reg, bytes_no}, _from, state = %{spi: spi}) do {:reply, Transciever.read_register(spi, reg, bytes_no), state} 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(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 = %Circuits.SPI.SPIDev{}}) do Circuits.SPI.close(spi) end def terminate(_, _), do: nil defp wait_for_data(_spi, _payload_size, 0), do: {:error, :no_data} defp wait_for_data(spi, payload_size, timeout_in_secs) do case Transciever.receive(spi, payload_size) do {:error, :no_data} -> Process.sleep(1000) wait_for_data(spi, payload_size, timeout_in_secs - 1) {:error, :unexpected_data_in_fifo} -> {:error, :unexpected_data_in_fifo} {:ok, data} -> {:ok, data} end end end