-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathMindWaveConnection.hs
More file actions
345 lines (305 loc) · 12.2 KB
/
Copy pathMindWaveConnection.hs
File metadata and controls
345 lines (305 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module MindWaveConnection (
readMind,
initialMindWaveInfo,
MindWaveInfo (..),
disconnect
) where
import System.Hardware.Serialport (SerialPort)
import qualified System.Hardware.Serialport as SP
import GHC.IO.Handle
import Foreign
import qualified Data.ByteString as B
import Data.Word
import Data.Char (intToDigit, toUpper)
import qualified Control.Exception as Ex
import Control.Monad.State
import System.Console.ANSI
import Data.IORef
-- | The default location the MindWave serial port is mounted (on my linux system)
mindWaveDev :: FilePath
mindWaveDev = "/dev/ttyUSB0"
-- | The serial port settings required in the MindWave spec
mindWaveSerialSettings :: SP.SerialPortSettings
mindWaveSerialSettings = SP.defaultSerialSettings { SP.commSpeed = SP.CS115200 }
-- | Sends the "magic word" for starting a connection to the MindWave.
-- Telling the MindWave to start sending data.
sendConnect :: Word8 -> Word8 -> SerialPort -> IO Int
sendConnect a b sp = SP.send sp (B.pack [0xC0,a,b])
-- | Sends the "magic word" for ending a connection to the MindWave.
-- Telling the MindWave to stop sending data.
sendDisconnect :: SerialPort -> IO Int
sendDisconnect sp = SP.send sp (B.pack [0xC1])
-- | Sends the "magic word" for starting a connection to the MindWave.
-- Telling the MindWave to start sending data.
sendAutoConnect :: SerialPort -> IO Int
sendAutoConnect sp = SP.send sp (B.pack [0xC2])
-- | Opens a connection to a MindWave on the default serial port location
openMindWave :: IO SerialPort
openMindWave = do
print $ "Opening MindWave on serial port " ++ mindWaveDev
sp <- SP.openSerial mindWaveDev mindWaveSerialSettings
sendAutoConnect sp
return sp
-- | Closes a connection to a MindWave on the default serial port location
closeMindWave :: SerialPort -> IO ()
closeMindWave sp = do
print $ "Disconnecting from MindWave."
sendDisconnect sp
SP.closeSerial sp
-- | A type synonym for a StateT containing the serial port in use, and the
-- latest info from the MindWave, on top of the IO Monad
type MindWave a = StateT (SerialPort,MindWaveInfo) IO a
-- | A getter function for the SerialPort
getSerialPort :: MindWave SerialPort
getSerialPort = do
(sp,_) <- get
return sp
-- | A bracketing function to that first connects to the MindWave, performs
-- the given action, and finally disconnects from the MindWave
withMindWave :: MindWave a -> IO a
withMindWave mwa = Ex.bracket openMindWave closeMindWave $ \sp ->
evalStateT mwa (sp,initialMindWaveInfo)
-- | A standalone IO action for disconnecting from the MindWave.
-- (This is useful if the connection is somehow left open after the program terminates)
disconnect :: IO ()
disconnect = do
sp <- SP.openSerial mindWaveDev mindWaveSerialSettings
print $ "Disconnecting from MindWave."
sendDisconnect sp
SP.closeSerial sp
-- | A pretty-print function for Word8
showWord8 :: Word8 -> String
showWord8 b = "0x" ++ [hi,lo]
where
hiDigit = b `div` 16
loDigit = b - (hiDigit * 16)
lo = toUpper (intToDigit (fromIntegral loDigit))
hi = toUpper (intToDigit (fromIntegral hiDigit))
-- | Generates a MindWave specific Checksum from the given list of Word8
checksum :: [Word8] -> Word8
checksum bs = complement (foldr (+) 0 bs)
-- | A packet of information from the MindWave is simple a list of Word8
data MindWavePacket = MindWavePacket [Word8]
{-
instance Show MindWavePacket where
show (MindWavePacket pl) = "0xAA 0xAA " ++ showWord8 (fromIntegral $ length pl) ++ concat (map (\b -> ' ':showWord8 b) pl) ++ " " ++ showWord8 (checksum pl)
printPackets :: MindWave ()
printPackets = sequence_ $ repeat $ readMindWavePacket >>= lift . print
-}
-- | A function for reading a packet of information from the MindWave
readMindWavePacket :: MindWave MindWavePacket
readMindWavePacket = do
sync <- readByte
case sync of
0xAA -> do
sync <- readByte
case sync of
0xAA -> readPayload
other -> readMindWavePacket
other -> readMindWavePacket
-- | A helper function for reading the payload from the MindWave
readPayload :: MindWave MindWavePacket
readPayload = do
datalength <- readByte
case (datalength > 169) of
True -> readMindWavePacket
False -> do
pl <- readBytes datalength
cs <- readByte
case (checksum pl == cs) of
False -> readMindWavePacket
True -> return (MindWavePacket pl)
-- | A helper function for reading a single Word8 from the MindWave
readByte :: MindWave Word8
readByte = do
sp <- getSerialPort
bs <- lift $ SP.recv sp 1
case (B.unpack bs) of
[] -> readByte
[x] -> return x
_ -> error "Multiple bytes returned"
-- | A helper function for reading a specifc number of Word8 from the MindWave
readBytes :: Word8 -> MindWave [Word8]
readBytes x = do
bs <- doReadBytes x []
return $ reverse bs
-- | A recursive hepler function for reading a specifc number of Word8 from the MindWave
doReadBytes :: Word8 -> [Word8] -> MindWave [Word8]
doReadBytes 0 bs = return bs
doReadBytes n bs = do
byte <- readByte
doReadBytes (n-1) (byte:bs)
-- | A row of date from the MindWave
data MindWaveDataRow = MindWaveDataRow Int Word8 [Word8]
instance Show MindWaveDataRow where
show (MindWaveDataRow ec c v) = show ec ++ ":" ++ showWord8 c ++ concat (map (\b -> ' ':showWord8 b) v)
-- | Parse a packet of information from the MindWave into a list of data rows.
parseMindWavePacket :: MindWavePacket -> [MindWaveDataRow]
parseMindWavePacket (MindWavePacket []) = []
parseMindWavePacket (MindWavePacket pl) = (MindWaveDataRow excode code value):parseMindWavePacket (MindWavePacket rest)
where
(excode,pl') = countExcodes pl 0
(code,pl'') = (head pl', tail pl')
(value, rest) = if code > 0x7F
then (take (fromIntegral $ head pl'') (tail pl''), drop (fromIntegral $ head pl'') (tail pl''))
else ([head pl''], tail pl'')
-- | A helper function for counting the number of "Excodes" at the begining of a list of Word8
countExcodes :: [Word8] -> Int -> (Int, [Word8])
countExcodes (0x55:bs) n = countExcodes bs (n+1)
countExcodes bs n = (n, bs)
{-
prettyPrintData :: MindWave ()
prettyPrintData = sequence_ $ repeat $ do
mwp <- readMindWavePacket
lift $ print (map prettyPrint (parseMindWavePacket mwp))
printData :: MindWave ()
printData = (lift $ clearScreen >> hideCursor) >> (sequence_ $ repeat $ do
mwp <- readMindWavePacket
(sp,mwi) <- get
let mwi' = foldr updateState mwi (parseMindWavePacket mwp)
lift $ setCursorPosition 0 0 >> print mwi'
put (sp,mwi'))
main :: IO ()
main = withMindWave printData
prettyPrint :: MindWaveDataRow -> String
prettyPrint mwp@(MindWaveDataRow excode code value) =
case excode of
0 -> case code of
0x02 -> "POOR_SIGNAL Quality: " ++ show (head value)
0x04 -> "ATTENTION eSense: " ++ show (head value)
0x05 -> "MEDITATION eSense: " ++ show (head value)
0x16 -> "Blink Strength: " ++ show (head value)
0x55 -> "EXCODE " ++ show value
0x80 -> "RAW: " ++ hexValue 2 value
0x83 -> "ASIC_EEG_POWER: " ++ eegValue value
0xAA -> "SYNC " ++ show value
0xD0 -> "Connected to Headset " ++ hexValue 2 value
0xD1 -> case (length value) of
0 -> "No Headsets found"
2 -> "Headset " ++ hexValue 2 value ++ " not found"
_ -> show mwp
0xD2 -> "Disconnected from Headset " ++ hexValue 2 value
0xD3 -> "Request Denied"
0xD4 -> case (head value) of
0 -> "Dongle in Standy Mode"
_ -> "Scanning for Headset"
_ -> show mwp
_ -> show mwp
-}
-- | A helper function for pretty printing Word8 in Hex format
hexValue :: Int -> [Word8] -> String
hexValue n ws = "0x" ++ concat (map (\w -> drop 2 (showWord8 w)) (take n ws))
{-
eegValue :: [Word8] -> String
eegValue [d1,d2,d3,t1,t2,t3,la1,la2,la3,ha1,ha2,ha3,lb1,lb2,lb3,hb1,hb2,hb3,lg1,lg2,lg3,mg1,mg2,mg3] = d ++ t ++ la ++ ha ++ lb ++ hb ++ lg ++ mg
where
d = "delta:" ++ hexValue 3 [d1,d2,d3] ++ " "
t = "theta:" ++ hexValue 3 [t1,t2,t3] ++ " "
la = "low-alpha:" ++ hexValue 3 [la1,la2,la3] ++ " "
ha = "high-alpha:" ++ hexValue 3 [ha1,ha2,ha3] ++ " "
lb = "low-beta:" ++ hexValue 3 [lb1,lb2,lb3] ++ " "
hb = "high-beta:" ++ hexValue 3 [hb1,hb2,hb3] ++ " "
lg = "low-gamma:" ++ hexValue 3 [lg1,lg2,lg3] ++ " "
mg = "mid-gamma:" ++ hexValue 3 [mg1,mg2,mg3] ++ " "
eegValue ws = "error " ++ show ws
-}
-- | MindWaveInfo consits of the data that is sent from the MindWave
data MindWaveInfo = MindWaveInfo {
dongle_status :: String,
last_message :: String,
poor_signal :: String,
attention :: String,
meditation :: String,
blink_strength :: String,
raw_value :: String,
delta :: String,
theta :: String,
low_alpha :: String,
high_alpha :: String,
low_beta :: String,
high_beta :: String,
low_gamma :: String,
mid_gamma :: String,
unknown_code :: String
}
{-
instance Show MindWaveInfo where
show mwi =
"dongle_status: " ++ dongle_status mwi ++ "\n" ++
"last_message: " ++ last_message mwi ++ "\n" ++
"poor_signal: " ++ poor_signal mwi ++ "\n" ++
"attention: " ++ attention mwi ++ "\n" ++
"meditation: " ++ meditation mwi ++ "\n" ++
"blink_strength: " ++ blink_strength mwi ++ "\n" ++
"raw_value: " ++ raw_value mwi ++ "\n" ++
"delta: " ++ delta mwi ++ "\n" ++
"theta: " ++ theta mwi ++ "\n" ++
"low_alpha: " ++ low_alpha mwi ++ "\n" ++
"high_alpha: " ++ high_alpha mwi ++ "\n" ++
"low_beta: " ++ low_beta mwi ++ "\n" ++
"high_beta: " ++ high_beta mwi ++ "\n" ++
"low_gamma: " ++ low_gamma mwi ++ "\n" ++
"mid_gamma: " ++ mid_gamma mwi ++ "\n" ++
"unknown_code: " ++ unknown_code mwi ++ "\n"
-}
-- | Before connecting to the MindWave, we have no information.
initialMindWaveInfo :: MindWaveInfo
initialMindWaveInfo = MindWaveInfo "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
-- | For any data row we recieve, we can update the info accordingly
updateState :: MindWaveDataRow -> MindWaveInfo -> MindWaveInfo
updateState mwp@(MindWaveDataRow excode code value) mwi =
case excode of
0 -> case code of
0x02 -> mwi { poor_signal = show (head value) ++ " "}
0x04 -> mwi { attention = show (head value) ++ " "}
0x05 -> mwi { meditation = show (head value) ++ " "}
0x16 -> mwi { blink_strength = show (head value) ++ " "}
0x80 -> mwi { raw_value = hexValue 2 value }
0x83 -> updateEegValue value mwi
0xD0 -> mwi { dongle_status = "Connected to Headset " ++ hexValue 2 value }
0xD1 -> case (length value) of
0 -> mwi { last_message = "No Headsets found " }
2 -> mwi { last_message = "Headset " ++ hexValue 2 value ++ " not found" }
_ -> mwi { unknown_code = show mwp }
0xD2 -> mwi { last_message = "Disconnected from Headset " ++ hexValue 2 value }
0xD3 -> mwi { last_message = "Request Denied" }
0xD4 -> case (head value) of
0 -> mwi { dongle_status = "Dongle in Standy Mode" }
_ -> mwi { dongle_status = "Scanning for Headset " }
_ -> mwi { unknown_code = show mwp }
_ -> mwi { unknown_code = show mwp }
-- | A helper function for extracting EEG specific values from the MindWave
updateEegValue :: [Word8] -> MindWaveInfo -> MindWaveInfo
updateEegValue [d1,d2,d3,t1,t2,t3,la1,la2,la3,ha1,ha2,ha3,lb1,lb2,lb3,hb1,hb2,hb3,lg1,lg2,lg3,mg1,mg2,mg3] mwi = mwi {
delta = d,
theta = t,
low_alpha = la,
high_alpha = ha,
low_beta = lb,
high_beta = hb,
low_gamma = lg,
mid_gamma = mg
}
where
d = hexValue 3 [d1,d2,d3]
t = hexValue 3 [t1,t2,t3]
la = hexValue 3 [la1,la2,la3]
ha = hexValue 3 [ha1,ha2,ha3]
lb = hexValue 3 [lb1,lb2,lb3]
hb = hexValue 3 [hb1,hb2,hb3]
lg = hexValue 3 [lg1,lg2,lg3]
mg = hexValue 3 [mg1,mg2,mg3]
updateEegValue ws mwi = mwi { unknown_code = show ws }
-- | Store MindWaveInfo in an IORef, and continually update it from the MindWave
readFromMindWave :: IORef MindWaveInfo -> MindWave ()
readFromMindWave ref = sequence_ $ repeat $ do
mwp <- readMindWavePacket
(sp,mwi) <- get
let mwi' = foldr updateState mwi (parseMindWavePacket mwp)
lift $ writeIORef ref mwi'
put (sp,mwi')
-- | An overall function for connecting the MindWave and continually updating the
-- info stored in an IORef
readMind :: IORef MindWaveInfo -> IO ()
readMind ref = withMindWave $ readFromMindWave ref