{-# LANGUAGE NamedFieldPuns #-} -- | Some convenience for building applications that want to read Emotiv data. -- -- You can use this if you are writing an EEG application and don't want to do -- the whole device selection / opening yourself. module Hemokit.Start ( EmotivArgs (..) , emotivArgsParser , parseModel , parseArgs , getEmotivDeviceFromArgs ) where import Options.Applicative import System.IO (stdin) import Hemokit hiding (serial) -- | Commonly used options for EEG command line applications. -- Mainly deals with input selection. data EmotivArgs = EmotivArgs { model :: EmotivModel -- ^ What model to use for decryption. , serial :: Maybe SerialNumber -- ^ What serial to use for decryption. -- Also allows to pick a certain device. , fromFile :: Maybe FilePath -- ^ Use the given device or dump file for input. -- If not given, HIDAPI is used. } deriving (Eq, Ord, Show) -- | EEG model command line parser. parseModel :: Monad m => String -> m EmotivModel parseModel s = case s of "consumer" -> return Consumer "developer" -> return Developer _ -> fail "Model is not valid. Must be 'consumer' or 'developer'." -- | Command line parser for EEG selection. See `EmotivArgs`. emotivArgsParser :: Parser EmotivArgs emotivArgsParser = EmotivArgs <$> option (eitherReader parseModel) ( long "model" <> metavar "MODEL" <> value Consumer <> help "Consumer or Developer model, Consumer by default" ) <*> (optional . option (maybeReader makeSerialNumberFromString "Serial number of has invalid format")) ( long "serial" <> metavar "SERIALNUMBER" <> help "The serial to use. If no --from-file is given, this will select the device" ) <*> (optional . strOption) ( long "from-file" <> metavar "PATH" <> help "The file path to read from (e.g. /dev/hidraw0 or myfile.dump)" ) where maybeReader :: (String -> Maybe a) -> String -> ReadM a maybeReader mbFn msg = eitherReader (maybe (fail msg) pure . mbFn) -- | Runs a command line parser. The given program description is used for the -- --help message. parseArgs :: String -> Parser a -> IO a parseArgs programDescription parser = execParser $ info (helper <*> parser) (progDesc programDescription) -- | Depending on some common EEG-choice-related user input, list devices or -- try to open the correct device. getEmotivDeviceFromArgs :: EmotivArgs -> IO (Either String EmotivDevice) getEmotivDeviceFromArgs EmotivArgs{ model, serial, fromFile } = case fromFile of -- File given, use device file / file handle Just f | Just s <- serial -> Right <$> if f == "-" then openEmotivDeviceHandle model s stdin else openEmotivDeviceFile model s f | otherwise -> fail "A serial number must be provided when using --from-file" -- No file given, use HIDAPI to select the device Nothing -> do devices <- getEmotivDevices case devices of [] -> fail "No devices found." _ -> case serial of -- Pick the last device with the serial the user wants Just s -> case reverse [ d | d <- devices, deviceInfoSerial d == Just s ] of [] -> fail $ "No device with serial " ++ show s d:_ -> print d >> Right <$> openEmotivDevice model d -- TODO Do smarter auto detection, e.g. filter for Emotiv vendorIDs -- or the "EPOC BCI" product string. -- The user selected no serial, we just use the last device _ -> print (last devices) >> Right <$> openEmotivDevice model (last devices)