{-# LANGUAGE OverloadedStrings, ForeignFunctionInterface #-}

-- |Library for controlling the GPIO pins on a Raspberry Pi (or any system using the Broadcom 2835 SOC). It is constructed
-- as a FFI wrapper over the BCM2835 library by Mike McCauley.
module System.RaspberryPi.GPIO (
    -- *Data types
    Pin(..),
    PinMode(..),
    LogicLevel,
    Address,
    SPIBitOrder(..),
    SPIPin(..),
    CPOL,
    CPHA,
    -- *General functions
    withGPIO,
    -- *GPIO specific functions
    setPinFunction,
    readPin,
    writePin,
    -- *I2C specific functions
    withI2C,
    setI2cClockDivider,
    setI2cBaudRate,
    writeI2C,
    readI2C,
    writeReadRSI2C,
    -- *SPI specific functions
    withAUXSPI,
    withSPI,
    chipSelectSPI,
    setBitOrderSPI,
    setChipSelectPolaritySPI,
    setClockDividerAUXSPI,
    setClockDividerSPI,
    setDataModeSPI,
    transferAUXSPI,
    transferSPI,
    transferManySPI
    ) where

-- FFI wrapper over the I2C portions of the BCM2835 library by Mike McCauley, also some utility functions to
-- make reading and writing simpler

import Control.Applicative ((<$>))
import Control.Exception
import Foreign
import Foreign.C
import Foreign.C.String
import qualified Data.ByteString as BS
import Data.Maybe
import Data.Tuple
import GHC.IO.Exception

------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------- Data types ---------------------------------------------------------------------------
-- |This describes the pins on the Raspberry Pi boards. Since the BCM2835 SOC internally uses different numbers (and these numbers 
-- differ between versions, the library internally translates this pin number to the correct number.
data Pin =  -- |Pins for the P1 connector of the V2 revision of the Raspberry Pi
            Pin03|Pin05|Pin07|Pin08|Pin10|Pin11|Pin12|Pin13|Pin15|Pin16|Pin18|Pin19|Pin21|Pin22|Pin23|Pin24|Pin26|Pin36|
            -- |Pins for the P5 connector of the V2 revision of the Raspberry Pi
            PinP5_03|PinP5_04|PinP5_05|PinP5_06|
            -- |Pins for the P1 connector of the V1 revision of the Raspberry Pi
            PinV1_03|PinV1_05|PinV1_07|PinV1_08|PinV1_10|PinV1_11|PinV1_12|PinV1_13|PinV1_15|PinV1_16|PinV1_18|PinV1_19|PinV1_21|
            PinV1_22|PinV1_23|PinV1_24|PinV1_26
            deriving (Eq,Show)

-- |A GPIO pin can be either set to input mode, output mode or an alternative mode.
data PinMode = Input | Output | Alt0 | Alt1 | Alt2 | Alt3 | Alt4 | Alt5 deriving (Eq,Show)

instance Enum PinMode where -- bit strange, but just deriving Enum doesn't work because the numbers don't monotonically ascend
    fromEnum = fromJust . flip lookup table
    toEnum = fromJust . flip lookup (map swap table)
table = [(Input, 0), (Output, 1), (Alt0, 4), (Alt1, 5), (Alt2, 6), (Alt3, 7), (Alt4, 3), (Alt5, 2)]

-- |This describes the address of an I2C slave.
type Address = Word8 --adress of an I2C slave

-- |Either high or low.
type LogicLevel = Bool

-- |Specifies the SPI data bit ordering.
data SPIBitOrder = LSBFirst | MSBFirst

-- |This describes which Chip Select pins are asserted (used in SPI communications).
data SPIPin = CS0 | CS1 | CS2 | CSNONE deriving (Eq, Show, Enum)

-- |Clock polarity (CPOL) for SPI transmissions.
type CPOL = Bool

-- |Clock phase (CPHA) for SPI transmissions.
type CPHA = Bool

------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------ Foreign imports -------------------------------------------------------------------------

----------------------------------------- Library functions ------------------------------------------------------------------------
--initialises /dev/mem and obtaining the proper pointers to device registers. Don't use any other functions if this fails!
foreign import ccall unsafe "bcm2835.h bcm2835_init" initBCM2835 :: IO Int
--deallocates any memory, closes /dev/mem and stops the library in general
foreign import ccall unsafe "bcm2835.h bcm2835_close" stopBCM2835 :: IO Int
--sets debug level
foreign import ccall unsafe "bcm2835.h bcm2835_set_debug" setDebugBCM2835 :: CUChar -> IO ()

---------------------------------------- Basic GPIO functions ----------------------------------------------------------------------
-- setFunction (input/output)
foreign import ccall unsafe "bcm2835.h bcm2835_gpio_fsel" c_setPinFunction    :: CUChar -> CUChar -> IO ()
-- setPin (zet een outputpin hoog/laag)
foreign import ccall unsafe "bcm2835.h bcm2835_gpio_write" c_writePin         :: CUChar -> CUChar -> IO ()
-- readPin (geeft weer of een pin hoog/laag is)
foreign import ccall unsafe "bcm2835.h bcm2835_gpio_lev" c_readPin            :: CUChar -> IO CUChar

------------------------------------------- I2C functions --------------------------------------------------------------------------
--inits the i2c pins
foreign import ccall unsafe "bcm2835.h bcm2835_i2c_begin" initI2C   :: IO ()
--resets the i2c pins
foreign import ccall unsafe "bcm2835.h bcm2835_i2c_end"   stopI2C   :: IO ()

--sets the slave address used
foreign import ccall unsafe "bcm2835.h bcm2835_i2c_setSlaveAddress" c_setSlaveAddressI2C ::   CUChar -> IO ()
--sets the I2C bus clock divider (and thus, the speed)
foreign import ccall unsafe "bcm2835.h bcm2835_i2c_setClockDivider" c_setClockDividerI2C ::   CUShort -> IO ()
--sets the I2C bus baud rate (100000 for the default 100khz)
foreign import ccall unsafe "bcm2835.h bcm2835_i2c_set_baudrate"    c_setBaudRateI2C ::       CUInt -> IO ()

-- writes some bytes to the bus
foreign import ccall unsafe "bcm2835.h bcm2835_i2c_write" c_writeI2C :: CString -> CUInt -> IO CUChar
--read some bytes from the bus
foreign import ccall unsafe "bcm2835.h bcm2835_i2c_read" c_readI2C :: CString -> CUShort -> IO CUChar
--reads a certain register with the repeated start method
foreign import ccall unsafe "bcm2835.h bcm2835_i2c_write_read_rs" c_writeReadRSI2C :: CString -> CUInt -> CString -> CUInt -> IO CUChar

------------------------------------------- SPI functions --------------------------------------------------------------------------
--inits the SPI pins
foreign import ccall unsafe "bcm2835.h bcm2835_spi_begin" initSPI   :: IO ()
--resets the SPI pins
foreign import ccall unsafe "bcm2835.h bcm2835_spi_end"   stopSPI   :: IO ()

--Transfers one byte to and from the currently selected SPI slave
foreign import ccall unsafe "bcm2835.h bcm2835_spi_transfer"        c_transferSPI :: CUChar -> IO CUChar
--Transfers multiple bytes to and from the currently selected SPI slave
foreign import ccall unsafe "bcm2835.h bcm2835_spi_transfern"      c_transferManySPI :: CString -> CUInt -> IO ()
--Changes the chip select pins
foreign import ccall unsafe "bcm2835.h bcm2835_spi_chipSelect"      c_chipSelectSPI :: CUChar -> IO ()

--Set the bit order to be used for transmit and receive.
foreign import ccall unsafe "bcm2835.h bcm2835_spi_setBitOrder"           c_setBitOrder :: CUChar -> IO ()
--Sets whether SPI Chip Select pulls pins high or low.
foreign import ccall unsafe "bcm2835.h bcm2835_spi_setChipSelectPolarity" c_setChipSelectPolarity :: CUChar -> CUChar -> IO ()
--Sets the SPI clock divider and therefore the SPI clock speed.
foreign import ccall unsafe "bcm2835.h bcm2835_spi_setClockDivider"       c_setClockDividerSPI :: CUShort -> IO ()
--Sets the data mode used (phase/polarity)
foreign import ccall unsafe "bcm2835.h bcm2835_spi_setDataMode"     c_setDataModeSPI :: CUChar -> IO ()

----------------------------------------- AUX SPI functions ------------------------------------------------------------------------
--inits the AUX SPI pins
foreign import ccall unsafe "bcm2835.h bcm2835_aux_spi_begin" initAUXSPI   :: IO Int
--resets the AUX SPI pins
foreign import ccall unsafe "bcm2835.h bcm2835_aux_spi_end"   stopAUXSPI   :: IO ()

--Transfers one byte to and from the currently selected SPI slave
foreign import ccall unsafe "bcm2835.h bcm2835_aux_spi_transfer"       c_transferAUXSPI :: CUChar -> IO CUChar

--Sets the SPI clock divider and therefore the SPI clock speed.
foreign import ccall unsafe "bcm2835.h bcm2835_aux_spi_setClockDivider"     c_setClockDividerAUXSPI :: CUShort -> IO ()

------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------ Exportable functions --------------------------------------------------------------------

------------------------------------------- Utility functions ----------------------------------------------------------------------
-- |Any IO computation that accesses the GPIO pins using this library should be wrapped with this function; ie @withGPIO $ do foo@.
-- It prepares the file descriptors to /dev/mem and makes sure everything is safely deallocated if an exception occurs. The behavior
-- when accessing the GPIO outside of this function is undefined.
withGPIO :: IO a -> IO a
withGPIO f = bracket    initBCM2835
                        (const stopBCM2835) --const because you don't care about the output of initBCM2835
                        (\a -> if a==0 then throwIO ioe else f) -- init returning 0 is not good
                            where ioe = IOError Nothing IllegalOperation "GPIO: " "Unable to start GPIO." Nothing Nothing

-- |Any IO computation that uses the I2C bus using this library should be wrapped with this function; ie @withI2C $ do foo@.
-- It prepares the relevant pins for use with the I2C protocol and makes sure everything is safely returned to normal if an exception
-- occurs. If you only use the GPIO pins for I2C, you can do @withGPIO . withI2C $ do foo@ and it will work as expected. WARNING: 
-- after this function returns, the I2C pins will be set to Input, so use 'setPinFunction' if you want to use them for output.
withI2C :: IO a -> IO a
withI2C f = bracket_    initI2C
                        stopI2C
                        f

-- |Any IO computation that uses the SPI functionality using this library should be wrapped with this function; ie @withSPI $ do foo@.
-- It prepares the relevant pins for use with the SPI protocol and makes sure everything is safely returned to normal if an exception
-- occurs. If you only use the GPIO pins for SPI, you can do @withGPIO . withSPI $ do foo@ and it will work as expected. WARNING: 
-- after this function returns, the SPI pins will be set to Input, so use 'setPinFunction' if you want to use them for output.
withSPI :: IO a -> IO a
withSPI f = bracket_    initSPI
                        stopSPI
                        f
                        
-- Possible error results for I2C functions. (not exported)
actOnResult :: CUChar -> CStringLen -> IO BS.ByteString
actOnResult rr buf = case rr of
    0x01 -> throwIO $ IOError Nothing IllegalOperation "I2C: " "Received an unexpected NACK." Nothing Nothing
    0x02 -> throwIO $ IOError Nothing IllegalOperation "I2C: " "Received Clock Stretch Timeout." Nothing Nothing 
    0x04 -> throwIO $ IOError Nothing IllegalOperation "I2C: " "Not all data was read." Nothing Nothing
    0x00 -> BS.packCStringLen buf --convert C buffer to a bytestring


-- Mapping raspberry pi pin number to internal bmc2835 pin number, ugly solution, but meh. Also, the existence of mutiple versions
-- of the pin layout makes this the most elegant solution without resorting to state monads (which I don't want to do because that
-- would hamper the simplicity of having writePin and readPin be simple IO actions). As this function isn't exported anyway, the
-- user should never be troubled by all this.
getHwPin :: Pin -> CUChar
--P1 connector on V1 boards
getHwPin PinV1_03 = 0
getHwPin PinV1_05 = 1
getHwPin PinV1_07 = 4
getHwPin PinV1_08 = 14
getHwPin PinV1_10 = 15
getHwPin PinV1_11 = 17
getHwPin PinV1_12 = 18
getHwPin PinV1_13 = 21
getHwPin PinV1_15 = 22
getHwPin PinV1_16 = 23
getHwPin PinV1_18 = 24
getHwPin PinV1_19 = 10
getHwPin PinV1_21 = 9
getHwPin PinV1_22 = 25
getHwPin PinV1_23 = 11
getHwPin PinV1_24 = 8
getHwPin PinV1_26 = 7
--P1 connector on V2 boards
getHwPin Pin03 = 2
getHwPin Pin05 = 3
getHwPin Pin07 = 4
getHwPin Pin08 = 14
getHwPin Pin10 = 15
getHwPin Pin11 = 17
getHwPin Pin12 = 18
getHwPin Pin13 = 27
getHwPin Pin15 = 22
getHwPin Pin16 = 23
getHwPin Pin18 = 24
getHwPin Pin19 = 10
getHwPin Pin21 = 9
getHwPin Pin22 = 25
getHwPin Pin23 = 11
getHwPin Pin24 = 8
getHwPin Pin26 = 7
getHwPin Pin36 = 16
--for the P5 connector on V2 boards
getHwPin PinP5_03 = 28
getHwPin PinP5_04 = 29
getHwPin PinP5_05 = 30
getHwPin PinP5_06 = 31

------------------------------------------- GPIO functions -------------------------------------------------------------------------
-- |Sets the pin to either 'Input' or 'Output' mode.
setPinFunction :: Pin -> PinMode -> IO ()
setPinFunction pin mode = c_setPinFunction (getHwPin pin) (fromIntegral $ fromEnum mode)

-- |Sets the specified pin to either 'True' or 'False'.
writePin :: Pin -> LogicLevel -> IO () --wat gebeurt er als het geen output pin is?
writePin pin level = c_writePin (getHwPin pin) (fromIntegral $ fromEnum level)

-- |Returns the current state of the specified pin.
readPin :: Pin -> IO LogicLevel
readPin pin = (toEnum . fromIntegral) <$> c_readPin (getHwPin pin)

-------------------------------------------- I2C functions -------------------------------------------------------------------------
--not exported, only used internally
setI2cAddress :: Address -> IO ()
setI2cAddress a = c_setSlaveAddressI2C $ fromIntegral a

-- |Sets the clock divider for (and hence the speed of) the I2C bus.
setI2cClockDivider :: Word16 -> IO ()
setI2cClockDivider a = c_setClockDividerI2C $ fromIntegral a

-- |Sets the baud rate of the I2C bus.
setI2cBaudRate :: Word32 -> IO ()
setI2cBaudRate a = c_setBaudRateI2C $ fromIntegral a

-- |Writes the data in the 'ByteString' to the specified I2C 'Address'. Throws an IOException if an error occurs.
writeI2C :: Address -> BS.ByteString -> IO () --writes a bytestring to the specified address
writeI2C address by = BS.useAsCStringLen by $ \(bs,len) -> do
    setI2cAddress address
    readresult <- c_writeI2C bs (fromIntegral len)
    case readresult of
        0x01 -> throwIO $ IOError Nothing IllegalOperation "I2C: " "Received an unexpected NACK." Nothing Nothing
        0x02 -> throwIO $ IOError Nothing IllegalOperation "I2C: " "Received Clock Stretch Timeout." Nothing Nothing 
        0x04 -> throwIO $ IOError Nothing IllegalOperation "I2C: " "Not all data was read." Nothing Nothing
        0x00 -> return ()

-- |Reads num bytes from the specified 'Address'. Throws an IOException if an error occurs.
readI2C :: Address -> Int -> IO BS.ByteString --reads num bytes from the specified address
readI2C address num = allocaBytes (num+1) $ \buf -> do --is the +1 necessary??
    setI2cAddress address
    readresult <- c_readI2C buf (fromIntegral num)
    actOnResult readresult (buf, num)

-- |Writes the data in the 'ByteString' to the specified 'Address', then issues a "repeated start" (with no prior stop) and then 
-- reads num bytes from the same 'Address'. Necessary for devices that require such behavior, such as the MLX90620.
writeReadRSI2C :: Address -> BS.ByteString -> Int -> IO BS.ByteString
writeReadRSI2C address by num = BS.useAsCStringLen by $ \(bs,len) -> do --marshall the register-containing bytestring
    allocaBytes num $ \buf -> do --allocate a buffer for the response
        setI2cAddress address
        readresult <- c_writeReadRSI2C bs (fromIntegral len) buf (fromIntegral num)
        actOnResult readresult (buf, num)
        
-------------------------------------------- SPI functions -------------------------------------------------------------------------
-- |Sets the chip select pin(s). When a transfer is made with 'transferSPI' or 'transferManySPI', the selected pin(s) will be 
-- asserted during the transfer. 
chipSelectSPI :: SPIPin -> IO ()
chipSelectSPI pin = c_chipSelectSPI (fromIntegral . fromEnum $ pin)

-- |Sets the SPI clock divider and therefore the SPI clock speed.
setClockDividerSPI :: Word16 -> IO ()
setClockDividerSPI a = c_setClockDividerSPI $ fromIntegral a

-- |Set the bit order to be used for transmit and receive. The bcm2835 SPI0 only supports MSBFirst,
-- so if you select LSBFirst, the bytes will be reversed in software.
-- The library defaults to MSBFirst.
setBitOrderSPI :: SPIBitOrder -> IO ()
setBitOrderSPI LSBFirst = c_setBitOrder 0
setBitOrderSPI MSBFirst = c_setBitOrder 1

-- |Sets the chip select pin polarity for a given pin(s). When a transfer is made with 'transferSPI' or 'transferManySPI', the 
-- currently selected chip select pin(s) will be asserted to the LogicLevel supplied. When transfers are not happening, the chip 
-- select pin(s) return to the complement (inactive) value. 
setChipSelectPolaritySPI :: SPIPin -> LogicLevel -> IO ()
setChipSelectPolaritySPI pin level = c_setChipSelectPolarity (fromIntegral . fromEnum $ pin) (fromIntegral . fromEnum $ level)

-- |Sets the SPI clock polarity and phase (ie, CPOL and CPHA)
setDataModeSPI :: (CPOL,CPHA) -> IO ()
setDataModeSPI (False,False) = c_setDataModeSPI 0
setDataModeSPI (False,True)  = c_setDataModeSPI 1
setDataModeSPI (True,False)  = c_setDataModeSPI 2
setDataModeSPI (True,True)   = c_setDataModeSPI 3

-- |Transfers one byte to and from the currently selected SPI slave. Asserts the currently selected CS pins (as previously set by 
-- 'chipSelectSPI') during the transfer. Clocks the 8 bit value out on MOSI, and simultaneously clocks in data from MISO. Returns the 
-- read data byte from the slave.
transferSPI :: Word8 -> IO Word8
transferSPI input = fromIntegral <$> c_transferSPI (fromIntegral input)

-- |Transfers any number of bytes to and from the currently selected SPI slave, one byte at a time. Asserts the currently selected 
-- CS pins (as previously set by 'chipSelectSPI') during the transfer. Clocks 8 bit bytes out on MOSI, and simultaneously clocks in 
-- data from MISO.
transferManySPI :: [Word8] -> IO [Word8]
transferManySPI input = BS.useAsCStringLen (BS.pack input) $ \(buf,len) -> do --convert input list to bytestring and from there to CString
    --returns the read bytes in buf. Uses CStringLen because the responses might have zero bytes and this will influence the result if a
    --normal CString is used
    c_transferManySPI buf (fromIntegral len) --
    (BS.packCStringLen (buf,len)) >>= return . BS.unpack -- translate back from a buffer to a bytestring to a [Word8]
        
------------------------------------------ AUX SPI functions -----------------------------------------------------------------------

-- |Any IO computation that uses the AUX SPI functionality using this library should be wrapped with this function; ie @withAUXSPI $ do foo@.
-- It prepares the relevant pins for use with the SPI protocol and makes sure everything is safely returned to normal if an exception
-- occurs. If you only use the GPIO pins for SPI, you can do @withGPIO . withAUXSPI $ do foo@ and it will work as expected. WARNING: 
-- after this function returns, the SPI pins will be set to Input, so use 'setPinFunction' if you want to use them for output.
withAUXSPI :: IO a -> IO a
withAUXSPI f = bracket initAUXSPI
                       (const stopAUXSPI)
                       (\r -> if r==0 then throwIO ioe else f)
                            where ioe = IOError Nothing IllegalOperation "AUXAPI: " "Unable to start AUXAPI." Nothing Nothing

-- |Sets the AUX SPI clock divider and therefore the SPI clock speed.
setClockDividerAUXSPI :: Word16 -> IO ()
setClockDividerAUXSPI a = c_setClockDividerAUXSPI $ fromIntegral a

-- |Transfers one byte to and from the SPI slave. Asserts the CS2 pin during the transfer. Clocks the 8 bit value out
-- on MOSI, and simultaneously clocks in data from MISO. Returns the read data byte from the slave.
transferAUXSPI :: Word8 -> IO Word8
transferAUXSPI input = fromIntegral <$> c_transferAUXSPI (fromIntegral input)