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