#![no_std]
#![deny(missing_docs)]
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate bluetooth_hci as hci;
extern crate byteorder;
extern crate embedded_hal as emhal;
#[macro_use(block)]
extern crate nb;
use byteorder::{ByteOrder, LittleEndian};
use core::cmp::min;
use core::convert::TryFrom;
use core::marker::PhantomData;
use hci::host::HciHeader;
use hci::Controller;
mod cb;
mod command;
pub mod event;
mod opcode;
pub use command::gap;
pub use command::gatt;
pub use command::hal;
pub use command::l2cap;
pub use hci::host::{AdvertisingFilterPolicy, AdvertisingType, OwnAddressType};
#[derive(Debug, PartialEq)]
pub enum Error<SpiError, GpioError> {
Spi(SpiError),
Gpio(GpioError),
}
pub struct BlueNRG<'buf, SPI, OutputPin1, OutputPin2, InputPin, GpioError> {
chip_select: OutputPin1,
reset: OutputPin2,
data_ready: InputPin,
rx_buffer: cb::Buffer<'buf, u8>,
#[doc(hidden)]
_spi: PhantomData<SPI>,
#[doc(hidden)]
_gpio_error: PhantomData<GpioError>,
}
pub struct ActiveBlueNRG<'bnrg, 'spi, 'dbuf, SPI, OutputPin1, OutputPin2, InputPin, GpioError> {
d: &'bnrg mut BlueNRG<'dbuf, SPI, OutputPin1, OutputPin2, InputPin, GpioError>,
spi: &'spi mut SPI,
}
fn parse_spi_header<E>(header: &[u8; 5]) -> Result<(u16, u16), nb::Error<E>> {
const BNRG_READY: u8 = 0x02;
if header[0] == BNRG_READY {
Ok((
LittleEndian::read_u16(&header[1..]),
LittleEndian::read_u16(&header[3..]),
))
} else {
Err(nb::Error::WouldBlock)
}
}
enum Access {
Read,
Write,
}
impl Access {
fn byte(&self) -> u8 {
match self {
Access::Read => 0x0b,
Access::Write => 0x0a,
}
}
}
impl<'bnrg, 'spi, 'dbuf, SPI, OutputPin1, OutputPin2, InputPin, SpiError, GpioError>
ActiveBlueNRG<'bnrg, 'spi, 'dbuf, SPI, OutputPin1, OutputPin2, InputPin, GpioError>
where
SPI: emhal::blocking::spi::Transfer<u8, Error = SpiError>
+ emhal::blocking::spi::Write<u8, Error = SpiError>,
OutputPin1: emhal::digital::v2::OutputPin<Error = GpioError>,
OutputPin2: emhal::digital::v2::OutputPin<Error = GpioError>,
InputPin: emhal::digital::v2::InputPin<Error = GpioError>,
{
fn block_until_ready(
&mut self,
access_byte: u8,
) -> nb::Result<(u16, u16), Error<SpiError, GpioError>> {
loop {
let mut write_header = [access_byte, 0x00, 0x00, 0x00, 0x00];
self.spi
.transfer(&mut write_header)
.map_err(Error::Spi)
.map_err(nb::Error::Other)?;
match parse_spi_header(&write_header) {
Ok(lengths) => return Ok(lengths),
Err(nb::Error::WouldBlock) => {
self.d
.chip_select
.set_high()
.map_err(Error::Gpio)
.map_err(nb::Error::Other)?;
self.d
.chip_select
.set_low()
.map_err(Error::Gpio)
.map_err(nb::Error::Other)?;
}
Err(err) => return Err(err),
}
}
}
fn block_until_ready_for(
&mut self,
access: Access,
) -> nb::Result<u16, Error<SpiError, GpioError>> {
let (write_len, read_len) = self.block_until_ready(access.byte())?;
Ok(match access {
Access::Read => read_len,
Access::Write => write_len,
})
}
fn try_write(
&mut self,
header: &[u8],
payload: &[u8],
) -> nb::Result<(), Error<SpiError, GpioError>> {
if !header.is_empty() {
self.spi
.write(header)
.map_err(Error::Spi)
.map_err(nb::Error::Other)?;
}
if !payload.is_empty() {
self.spi
.write(payload)
.map_err(Error::Spi)
.map_err(nb::Error::Other)?;
}
Ok(())
}
fn read_available_data(&mut self) -> nb::Result<(), Error<SpiError, GpioError>> {
if !self
.d
.data_ready()
.map_err(Error::Gpio)
.map_err(nb::Error::Other)?
{
return Err(nb::Error::WouldBlock);
}
let read_len = self.block_until_ready_for(Access::Read)?;
let mut bytes_available = read_len as usize;
while bytes_available > 0 && self.d.rx_buffer.next_contiguous_slice_len() > 0 {
let transfer_count = min(
bytes_available,
self.d.rx_buffer.next_contiguous_slice_len(),
);
{
let rx = self.d.rx_buffer.next_mut_slice(transfer_count);
for byte in rx.iter_mut() {
*byte = 0;
}
self.spi
.transfer(rx)
.map_err(Error::Spi)
.map_err(nb::Error::Other)?;
}
bytes_available -= transfer_count;
}
Ok(())
}
fn write_command(
&mut self,
opcode: opcode::Opcode,
params: &[u8],
) -> nb::Result<(), Error<SpiError, GpioError>> {
const HEADER_LEN: usize = 4;
let mut header = [0; HEADER_LEN];
hci::host::uart::CommandHeader::new(opcode, params.len()).copy_into_slice(&mut header);
self.write(&header, ¶ms)
}
}
impl<'bnrg, 'spi, 'dbuf, SPI, OutputPin1, OutputPin2, InputPin, SpiError, GpioError> hci::Controller
for ActiveBlueNRG<'bnrg, 'spi, 'dbuf, SPI, OutputPin1, OutputPin2, InputPin, GpioError>
where
SPI: emhal::blocking::spi::Transfer<u8, Error = SpiError>
+ emhal::blocking::spi::Write<u8, Error = SpiError>,
OutputPin1: emhal::digital::v2::OutputPin<Error = GpioError>,
OutputPin2: emhal::digital::v2::OutputPin<Error = GpioError>,
InputPin: emhal::digital::v2::InputPin<Error = GpioError>,
{
type Error = Error<SpiError, GpioError>;
type Header = hci::host::uart::CommandHeader;
type Vendor = BlueNRGTypes;
fn write(&mut self, header: &[u8], payload: &[u8]) -> nb::Result<(), Self::Error> {
self.d
.chip_select
.set_low()
.map_err(Error::Gpio)
.map_err(nb::Error::Other)?;
let write_len = self.block_until_ready_for(Access::Write)?;
if (write_len as usize) < header.len() + payload.len() {
return Err(nb::Error::WouldBlock);
}
let result = self.try_write(header, payload);
self.d
.chip_select
.set_high()
.map_err(Error::Gpio)
.map_err(nb::Error::Other)?;
result
}
fn read_into(&mut self, buffer: &mut [u8]) -> nb::Result<(), Self::Error> {
let result = if buffer.len() > self.d.rx_buffer.size() {
self.d
.chip_select
.set_low()
.map_err(Error::Gpio)
.map_err(nb::Error::Other)?;
let r = self.read_available_data();
self.d
.chip_select
.set_high()
.map_err(Error::Gpio)
.map_err(nb::Error::Other)?;
r
} else {
Ok(())
};
if buffer.len() <= self.d.rx_buffer.size() {
self.d.rx_buffer.take_slice(buffer.len(), buffer);
Ok(())
} else if let Err(e) = result {
Err(e)
} else {
Err(nb::Error::WouldBlock)
}
}
fn peek(&mut self, n: usize) -> nb::Result<u8, Self::Error> {
if n >= self.d.rx_buffer.size() {
if !self
.d
.data_ready()
.map_err(Error::Gpio)
.map_err(nb::Error::Other)?
{
return Err(nb::Error::WouldBlock);
}
self.d
.chip_select
.set_low()
.map_err(Error::Gpio)
.map_err(nb::Error::Other)?;
let result = self.read_available_data();
self.d
.chip_select
.set_high()
.map_err(Error::Gpio)
.map_err(nb::Error::Other)?;
if n >= self.d.rx_buffer.size() {
if let Err(e) = result {
return Err(e);
}
}
}
if n < self.d.rx_buffer.size() {
Ok(self.d.rx_buffer.peek(n))
} else {
Err(nb::Error::WouldBlock)
}
}
}
pub struct BlueNRGTypes;
impl hci::Vendor for BlueNRGTypes {
type Status = event::Status;
type Event = event::BlueNRGEvent;
}
pub trait UartController<E>:
crate::gap::Commands<Error = E>
+ crate::gatt::Commands<Error = E>
+ crate::hal::Commands<Error = E>
+ crate::l2cap::Commands<Error = E>
+ bluetooth_hci::host::uart::Hci<E, crate::event::BlueNRGEvent, crate::event::BlueNRGError>
{
}
impl<T, E> UartController<E> for T where
T: crate::gap::Commands<Error = E>
+ crate::gatt::Commands<Error = E>
+ crate::hal::Commands<Error = E>
+ crate::l2cap::Commands<Error = E>
+ bluetooth_hci::host::uart::Hci<E, crate::event::BlueNRGEvent, crate::event::BlueNRGError>
{
}
impl<'buf, SPI, OutputPin1, OutputPin2, InputPin, GpioError>
BlueNRG<'buf, SPI, OutputPin1, OutputPin2, InputPin, GpioError>
where
OutputPin1: emhal::digital::v2::OutputPin<Error = GpioError>,
OutputPin2: emhal::digital::v2::OutputPin<Error = GpioError>,
InputPin: emhal::digital::v2::InputPin<Error = GpioError>,
{
pub fn new(
rx_buffer: &'buf mut [u8],
cs: OutputPin1,
dr: InputPin,
rst: OutputPin2,
) -> BlueNRG<'buf, SPI, OutputPin1, OutputPin2, InputPin, GpioError> {
BlueNRG {
chip_select: cs,
rx_buffer: cb::Buffer::new(rx_buffer),
data_ready: dr,
reset: rst,
_spi: PhantomData,
_gpio_error: PhantomData,
}
}
pub fn with_spi<'spi, T, F, E>(&mut self, spi: &'spi mut SPI, body: F) -> T
where
F: FnOnce(&mut ActiveBlueNRG<SPI, OutputPin1, OutputPin2, InputPin, GpioError>) -> T,
SPI: emhal::blocking::spi::transfer::Default<u8, Error = E>
+ emhal::blocking::spi::write::Default<u8, Error = E>,
{
let mut active =
ActiveBlueNRG::<SPI, OutputPin1, OutputPin2, InputPin, GpioError> { spi, d: self };
body(&mut active)
}
pub fn reset<T, Time>(&mut self, timer: &mut T, freq: Time) -> nb::Result<(), OutputPin2::Error>
where
T: emhal::timer::CountDown<Time = Time>,
Time: Copy,
{
self.reset.set_low().map_err(nb::Error::Other)?;
timer.start(freq);
block!(timer.wait()).unwrap();
self.reset.set_high().map_err(nb::Error::Other)?;
timer.start(freq);
block!(timer.wait()).unwrap();
Ok(())
}
fn data_ready(&self) -> Result<bool, InputPin::Error> {
self.data_ready.is_high()
}
}
#[derive(Clone)]
pub struct Version {
pub hw_version: u8,
pub major: u8,
pub minor: u8,
pub patch: u8,
}
pub trait LocalVersionInfoExt {
fn bluenrg_version(&self) -> Version;
}
impl<VS> LocalVersionInfoExt for hci::event::command::LocalVersionInfo<VS> {
fn bluenrg_version(&self) -> Version {
Version {
hw_version: (self.hci_revision >> 8) as u8,
major: (self.hci_revision & 0xFF) as u8,
minor: ((self.lmp_subversion >> 4) & 0xF) as u8,
patch: (self.lmp_subversion & 0xF) as u8,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum HardwareError {
SpiFraming,
RadioState,
TimerOverrun,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct InvalidHardwareError(pub u8);
impl TryFrom<u8> for HardwareError {
type Error = InvalidHardwareError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(HardwareError::SpiFraming),
1 => Ok(HardwareError::RadioState),
2 => Ok(HardwareError::TimerOverrun),
_ => Err(InvalidHardwareError(value)),
}
}
}