Packages

Library for receiving/transmitting data over nRF24L01+ transciever.

Current section

Files

Jump to
nrf24 lib nrf24.ex
Raw

lib/nrf24.ex

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