{-|
This module defines an interface to @CMPS-Nx@ digital compass sensor from <http://www.mindsensors.com/> according to
@CMPS-Nx-V20-User-Guide.pdf@ documentation.
-}

module Robotics.NXT.Sensor.Compass (
  -- * Initialization
  csInit,
  -- * Measurement
  csGetMeasurement,
  -- * Configuration
  csSetMode,
  Mode(..),
  -- * Constants
  csGetVersion,
  csGetVendorID,
  csGetDeviceID
) where

import Control.Monad

import Robotics.NXT
import Robotics.NXT.Data

data Mode =
    AutoTrigOn -- ^ AutoTrig (continuous measuring) on.
  | AutoTrigOff -- ^ AutoTrig (continuous measuring) off.
  | ResultByte -- ^ Result is a byte mapped to [0-255).
  | ResultInteger -- ^ Result is an integer mapped to [0-3600).
  | Frequency50 -- ^ Sampling frequency 50 Hz (Europe standard).
  | Frequency60 -- ^ Sampling frequency 60 Hz (USA standard).
{-
ADPA (Auto Detecting Parallel Architecture) mode allows multiple sensors to be connected on the same bus (port). This mode is not
available for default address of 0x02 which is used by this module.
  | ADPAOn -- ^ Auto Detecting Parallel Architecture on.
  | ADPAOff -- ^ Auto Detecting Parallel Architecture off.
-}
  | BeginCalibration -- ^ Begin calibration mode.
  | EndCalibration -- ^ End calibration mode.
  deriving (Bounded, Enum, Eq, Ord, Read, Show)

deviceAddress :: DeviceAddress
deviceAddress = 0x02

{-|
Initializes sensor on the given input port. It sets 'Mode' to 'ResultInteger'.
-}
csInit :: InputPort -> NXT ()
csInit input = do
  setInputModeConfirm input Lowspeed9V RawMode
  ready <- lowspeedGetStatus input
  when (ready > 0) $ lowspeedRead input >> return () -- clears any possible pending data in the buffer
  csSetMode input ResultInteger

csReadString :: InputPort -> Command -> RxDataLength -> NXT String
csReadString input command size = do
  lowspeedWrite input size [deviceAddress, command]
  s <- lowspeedRead input
  return $ dataToString0 s

-- Constants

{-|
Reads software version string (@V2.00@).
-}
csGetVersion :: InputPort -> NXT String
csGetVersion input = csReadString input 0x00 8

{-|
Reads vendor ID string (@mndsnsrs@).
-}
csGetVendorID :: InputPort -> NXT String
csGetVendorID input = csReadString input 0x08 8

{-|
Reads device ID string (@CMPS@).
-}
csGetDeviceID :: InputPort -> NXT String
csGetDeviceID input = csReadString input 0x10 8

-- Configuration

{-|
Sets current mode of operation.
-}
csSetMode :: InputPort -> Mode -> NXT ()
csSetMode input mode =
  lowspeedWriteConfirm input 0 $ [deviceAddress, 0x41] ++ command
    where command = case mode of
                      AutoTrigOn       -> [0x41, 0x02]
                      AutoTrigOff      -> [0x53, 0x01]
                      ResultByte       -> [0x42]
                      ResultInteger    -> [0x49]
                      Frequency50      -> [0x45]
                      Frequency60      -> [0x55]
{-
Currently not used.
                      ADPAOn           -> [0x4E]
                      ADPAOff          -> [0x4F]
-}
                      BeginCalibration -> [0x43]
                      EndCalibration   -> [0x44]

-- Measurement

{-|
Gets last measurement. Based on current 'Mode' it return value in [0-255) ('ResultByte' mode) or [0-3600) ('ResultInteger' mode) range.
-}
csGetMeasurement :: InputPort -> NXT Measurement
csGetMeasurement input = do
  lowspeedWrite input 2 [deviceAddress, 0x42]
  angle <- lowspeedRead input
  return $ fromUWord angle