bluetooth-socket
1 Introduction
1.1 RAW Socket Capability
2 API
2.1 Types
Hci  Device  Info
Hci  Device  Stats
2.2 Controller Information
hci-socket
hci-socket-close
hci-devices
hci-device-info
2.3 High Level Controller Communication
hci-connect
hci-disconnect
hci-initialize-socket
hci-read-semaphore
3 Example Use
8.12

bluetooth-socket🔗ℹ

Raymond Racine <ray.racine@gmail.com>

1 Introduction🔗ℹ

This library offers support for low level socket communication to a Bluetooth Hardware Controller. Specifically a AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI socket.

Via a non-’bind’ed hci socket querying the number of Bluetooth controllers (hci0, hci1, ...) and their device info is supported.

By ’bind’ing the socket to a specific controller low level Bluetooth HCI packets may be sent to the controller supporting the development of a full/paritial implementation of the Bluetooth Specification stack in Racket.

Currently only Linux is supported. The intent by making this library its own small collection is to allow for eventual additional O/S support.

1.1 RAW Socket Capability🔗ℹ

Use of RAW sockets is priviledged in Linux. To avoid having to run as root or via sudo set a raw capability on the racket binary as follows: sudo setcap ’cap_net_raw,cap_net_admin+eip’ /usr/local/racket/bin/racket

2 API🔗ℹ

 (require bluetooth-socket) package: bluetooth-socket

2.1 Types🔗ℹ

struct

(struct HciDeviceInfo (dev-id
    name
    bd-addr
    flags
    type
    features
    pkt-type
    link-policy
    link-mode
    acl-mtu
    acl-pkts
    sco-mtu
    sco-pkts
    stats)
    #:extra-constructor-name make-HciDeviceInfo)
  dev-id : exact-positive-integer?
  name : string?
  bd-addr : bytes?
  flags : exact-positive-integer?
  type : exact-positive-integer?
  features : bytes?
  pkt-type : exact-positive-integer?
  link-policy : exact-positive-integer?
  link-mode : exact-positive-integer?
  acl-mtu : exact-positive-integer?
  acl-pkts : exact-positive-integer?
  sco-mtu : exact-positive-integer?
  sco-pkts : exact-positive-integer?
  stats : HciDeviceStats?
The low level FFI data from a O/S ioctl call. For interpretation of the low level values see your O/S and Bluetooth documentation.

A goal is to offer a high level controller device info routine which fully parses out the this call at a later time (probably in a higher level collection).

struct

(struct HciDeviceStats (err-rx
    err-tx
    cmd-tx
    evt-rx
    acl-tx
    acl-rx
    sco-tx
    sco-rx
    byte-rx
    byte-tx)
    #:extra-constructor-name make-HciDeviceStats)
  err-rx : exact-positive-integer?
  err-tx : exact-positive-integer
  cmd-tx : exact-positive-integer
  evt-rx : exact-positive-integer
  acl-tx : exact-positive-integer
  acl-rx : exact-positive-integer
  sco-tx : exact-positive-integer
  sco-rx : exact-positive-integer
  byte-rx : exact-positive-integer
  byte-tx : exact-positive-integer
The low level FFI data from an O/S ioctl call. The receive and transmit packet stat for the low level Bluetooth packet types used for Host <-> Controller communication as definded in the Bluetooth specification.

Example of typical HCI controller device information.

(HciDeviceInfo 1 "hci1"
  #"\23q\332}\32\0" 5 1
  #"\377\377\217\376\333\377[\207"
  52472 15 32768 310 10 64 8
  (HciDeviceStats 0 0 77 79 0 0 0 0 1294 3287))

2.2 Controller Information🔗ℹ

Low level FFI call which opens a AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI socket. Returns an integer file descriptor (fd).

This fd can be used to query the current bluetooth controllers as well as their detailed device information and stats. See hci-connect to open a socket connection, bound to a specific controller, wrapped as standard Racket input and output ports suitable for Racket’s IO routines.

procedure

(hci-socket-close fd)  void?

  fd : exact-positive-integer?
Low level FFI call which closes the socket for the given file descriptor.

procedure

(hci-devices fd)  (listof exact-positive-integer?)

  fd : exact-positive-integer?
Low level FFI call that queries the O/S on the current installed controllers. Returns a list of the device ids (positive integers) of the available HCI controllers.

Assume your system has a builtin HCI controller and a USB HCI controller dongle. Typically these would show on a Linux system as hci0 and hci1. The list '(0 1) would be returned giving their device ids ’0’ and ’1’. This device ids can be subsequently used to obtain their device information. See hci-device-info.

procedure

(hci-device-info fd dev-id)  HciDeviceInfo?

  fd : integer?
  dev-id : integer?
Low level FFI call which queries the O/S for the detailed device information and stats for the given controller device id.

For example:

(define s (hci-socket))
(for ([dev-id (hci-devices s)])
  (print (hci-device-info s dev-id)))
(hci-socket-close s)

Will query for the number of available controllers and print out their raw detailed device information and stats.

2.3 High Level Controller Communication🔗ℹ

Procedures used to communicate with a specific controller. Communicating to a controller is done by creating a low level socket and then ’bind’ing the socket to a specific controller. The bound socket is then wrapped within standard Racket input and output ports and maybe utilized with normal I/O routines.

It is very important that the bound socket is initialized prior being used for any I/O. Initializing a socket involves a low level O/S setsockopt call to set a socket filter. Failure to set a socket filter will hang Racket when attempting any I/O via the socket. Currently the socket filter criteria is hardcoded in the library.

procedure

(hci-connect mode [dev-id])  
input-port? output-port?
  mode : symbol?
  dev-id : : = exact-positive-integer?
Connect to the given controller as specified by the device id. Currently only mode ’RAW is supported. Any other symbol used results in an error. Returns the values of a standard Racket input and output port suitable for use with standard Racket I/O routines.

IMPORTANT: Use of RAW sockets is priviledged in Linux systems. To avoid running Racket as root or via sudo set the capability of the Racket binary for raw sockets as follows:

sudo setcap ’cap_net_raw,cap_net_admin+eip’ /usr/local/racket/bin/racket

(define conn (hci-connect 'RAW 1))

procedure

(hci-disconnect fd inp outp)  void?

  fd : exact-positive-integer?
  inp : input-port?
  outp : output-port?
Low level call to fully close the underlying socket and wrapping Racket ports. If a low level O/S read/write semaphore was allocated for the file descriptor, it is also released.

procedure

(hci-initialize-socket fd)  void?

  fd : exact-positive-integer?
Low level FFI routine to set a hardcoded (but deemed suitable for now) filter for packet types and events on the socket fd via ’setsockopt’.

IMPORTANT: A controller ’bind’ed socket, one opened via a hci-connect call, must be initialized by this call prior to using the wrapping Racket ports in I/O routines. Failure to do so will hang Racket.

A fd from a hci-socket call is not ’bind’ed to a particular controller. Therefore it is usable only for getting controller information via hci-devices and hci-device-info and is not required to be intialized (have a sockopt filter set) via this call.

Creates/finds a low level read ready semaphore via scheme_fd_to_semaphore call. This semaphore when used sync detects if the socket has data available to read and avoid blocking on Rackets various read I/O routines. This semaphore is released on a hci-disconnect call.

3 Example Use🔗ℹ

Here is an example sketch of high level routines using this library to open a connection to an hci controller and reading inbound event packets from the controller. It shows use of the hci-connect and hci-read-semaphore to prevent hard blocking Racket in standard IO routines. The Racket standard IO ports from hci-connect which wrap the RAW HCI socket can be used in Racket IO read/write APIs.

(struct Hci-Connection (fd input-port output-port))
 
(define (hci-connection-read-semaphore conn)
  (hci-read-semaphore (Hci-Connection-fd conn)))
 
(define (hci-adapter-connect mode [dev-id 0])
  (let-values (([fd inp outp] (hci-connect mode dev-id)))
    (let* ([c (Hci-Connection fd inp outp)])
      (initialize-socket fd)
      (write-bytes-avail (encode-command-packet init-socket-event-mask-cmd-1) outp)
    c)))
 
(define (read-next-pkt c)
  (sync (hci-connection-read-semaphore c))
  (let ((hdr (read-bytes 8 (Hci-Connection-input-port c))))
    ...))
 
(define (inbound-read-loop c)
   (let ((pkt (read-next-pkt c)))
     (print pkt)
     (inbound-read-loop c)))
 
(define adapter-inbound-event-thread
  (let ((conn (hci-adapter-connect 'RAW 1)))
    (thread (λ () (inbound-read-loop conn)))))