Monday, August 18, 2014

Bluetooth on Haskell

I'm presenting a very early draft of my bluetooth library. As its name suggests, bluetooth is a Haskell frontend to low-level Bluetooth APIs, making it similar in spirit to Python's PyBluez and Java's BlueCove.

What it can do

Currently, bluetooth only supports Linux. It has the capability of running an RFCOMM server and client. Theoretically, it should also support L2CAP servers and clients, although this has not been tested yet.

What it will eventually do

I plan to have bluetooth support each of the GHC Tier 1 platforms—that is, Windows, OS X, Linux, and FreeBSD. I want to have the capability to run the full gamut of L2CAP and RFCOMM-related functions on each OS, as well as any additional OS-specific functionality.

Motivation

Bluetooth programming on Haskell is currently in a very primitive state. As of now, there are only two packages on Hackage that make any mention of Bluetooth (as far as I can tell):

  1. network hints in its Network.Socket module that there is an AF_BLUETOOTH socket address family. However, trying to use it with network will fail, since there is no corresponding Bluetooth SockAddr.
  2. simple-bluetooth by Stephen Blackheath offers some of what my own bluetooth package offers (namely, RFCOMM client capability on Windows and Linux).
However, there is currently no comprehensive, cross-platform Haskell Bluetooth library à la PyBluez or BlueCove. I want bluetooth to fill that niche.

How bluetooth works

bluetooth can be thought of as a wrapper around network. After all, Bluetooth programming is socket-based, so Network.Socket already provides most of what one needs to implement a Bluetooth server or client. There are several gotchas with Bluetooth programming, however:
  • Every major OS has a completely different Bluetooth software stack. For example, Linux uses BlueZ, and Windows has several different stacks, including Winsock and Widcomm. Therefore, bluetooth is not likely to work identically on every OS.
  • Windows in particular is challenging to support since several Winsock functions do not work correctly on the version of MinGW-w64 that is currently shipped with GHC for Windows (only the 64-bit version, no less). For this reason, I probably won't develop a Windows version of bluetooth until this issue is resolved.
It is recommended that you have a basic understanding of Bluetooth programming before attempting to use bluetooth. I recommend this introduction by Albert Huang.

Examples

The following are abridged examples of the RFCOMM client and server examples from the bluetooth repo.

RFCOMM server

module Main where
import Data.Set

import Network.Bluetooth
import Network.Socket

main :: IO ()
main = withSocketsDo $ do
    let uuid     = serviceClassToUUID SerialPort
        proto    = RFCOMM
        settings = defaultSDPInfo {
            sdpServiceName = Just "Roto-Rooter Data Router"
          , sdpProviderName = Just "Roto-Rooter"
          , sdpDescription = Just "An experimental plumbing router"
          , sdpServiceClasses = singleton SerialPort
          , sdpProfiles = singleton SerialPort
        }

    handshakeSock <- bluetoothSocket proto
    btPort <- bluetoothBindAnyPort handshakeSock anyAddr
    bluetoothListen handshakeSock 1
    service <- registerSDPService uuid settings proto btPort
    (connSock, connAddr) <- bluetoothAccept handshakeSock
    putStrLn $ "Established connection with address " ++ show connAddr

    message <- recv connSock 4096
    putStrLn $ "Received message! [" ++ message ++ "]"
    let response = reverse message
    respBytes <- send connSock response
    putStrLn $ "Sent response! " ++ show respBytes ++ " bytes."

    close connSock
    close handshakeSock
    closeSDPService service

RFCOMM client

module Main where

import Network.Bluetooth
import Network.Socket

import System.IO

main :: IO ()
main = withSocketsDo $ do
    let addr = read "12:34:56:78:90:00"
        port = 1
    
    sock <- bluetoothSocket RFCOMM
    bluetoothConnect sock addr port
    putStrLn "Established a connection!"
    
    putStr "Please enter a message to send: "
    hFlush stdout
    message <- getLine
    
    messBytes <- send sock message
    response <- recv sock 4096
    putStrLn $ "Received reponse! [" ++ reponse ++ "]"

    close sock