99import time
1010import logging
1111from time import sleep
12+ from datetime import datetime
1213from multiprocessing import Process
1314
1415import numpy as np
2627 EEG_CHANNELS ,
2728)
2829
30+ import socket , json , logging , struct
31+ from time import time
32+
2933
3034logger = logging .getLogger (__name__ )
3135
5458]
5559
5660
61+
5762class EEG :
5863 device_name : str
5964 stream_started : bool = False
@@ -66,8 +71,8 @@ def __init__(
6671 mac_addr = None ,
6772 other = None ,
6873 ip_addr = None ,
69- ch_names = None
70- ):
74+ ch_names = None ,
75+ make_logfile = False ):
7176 """The initialization function takes the name of the EEG device and determines whether or not
7277 the device belongs to the Muse or Brainflow families and initializes the appropriate backend.
7378
@@ -84,6 +89,7 @@ def __init__(
8489 self .mac_address = mac_addr
8590 self .ip_addr = ip_addr
8691 self .other = other
92+ self .make_logfile = make_logfile # currently only used for kf
8793 self .backend = self ._get_backend (self .device_name )
8894 self .initialize_backend ()
8995 self .n_channels = len (EEG_INDICES [self .device_name ])
@@ -92,23 +98,30 @@ def __init__(
9298 self .ch_names = ch_names
9399
94100 def initialize_backend (self ):
101+ # run this at initialization to get some
102+ # stream metadata into the eeg class
95103 if self .backend == "brainflow" :
96104 self ._init_brainflow ()
97105 self .timestamp_channel = BoardShim .get_timestamp_channel (self .brainflow_id )
98106 elif self .backend == "muselsl" :
99107 self ._init_muselsl ()
100- self ._muse_get_recent () # run this at initialization to get some
101- # stream metadata into the eeg class
108+ self ._muse_get_recent ()
109+ elif self .backend == "kernelflow" :
110+ self ._init_kf ()
102111
103112 def _get_backend (self , device_name ):
104113 if device_name in brainflow_devices :
105114 return "brainflow"
106115 elif device_name in ["muse2016" , "muse2" , "museS" ]:
107116 return "muselsl"
117+ elif device_name in ["kernelflow" ]:
118+ return "kernelflow"
119+
108120
109121 #####################
110122 # MUSE functions #
111123 #####################
124+
112125 def _init_muselsl (self ):
113126 # Currently there's nothing we need to do here. However keeping the
114127 # option open to add things with this init method.
@@ -186,9 +199,11 @@ def _muse_get_recent(self, n_samples: int = 256, restart_inlet: bool = False):
186199 df = pd .DataFrame (samples , index = timestamps , columns = ch_names )
187200 return df
188201
202+
189203 ##########################
190204 # BrainFlow functions #
191205 ##########################
206+
192207 def _init_brainflow (self ):
193208 """This function initializes the brainflow backend based on the input device name. It calls
194209 a utility function to determine the appropriate USB port to use based on the current operating system.
@@ -398,10 +413,111 @@ def _brainflow_get_recent(self, n_samples=256):
398413 # print (df)
399414 return df
400415
416+
417+
418+ ###########################
419+ # Kernel Flow functions #
420+ ###########################
421+
422+
423+ def _init_kf (self ):
424+
425+ self ._notes = None #muse_recent_inlet = None
426+
427+ # Grab the init time for tracking
428+ dtstr = str (datetime .now ()).replace (' ' , '_' ).split ('.' )[0 ].replace (':' , '-' )
429+
430+ # Initiate connection to trigger-recording port
431+ host = 'localhost' # could be another computer on network with a visible IP address?
432+ port = 6767
433+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
434+ sock .connect ((host , port ))
435+ self .kf_host = host
436+ self .kf_port = port
437+ self .kf_socket = sock
438+
439+ # Triggers history list
440+ self .kf_triggers_history = []
441+ self .kf_triggers_history .append ('Initiated connection: %s' % dtstr )
442+
443+ # Optionally maintain a logfile
444+ if self .make_logfile :
445+ self .kf_logfile_fname = 'eegexpy_kf_logfile__%s.txt' % dtstr
446+ self .kf_logfile_handle = open (self .kf_logfile_fname , 'a' )
447+ self .kf_logfile_handle .write ('KF TRIGGERS LOGFILE %s \n \n ' % dtstr )
448+
449+
450+ def _start_kf (self ): #:, duration):
451+
452+ kf_start_timestamp = int (time ()* 1e6 )
453+
454+ # Send first data packet
455+ self .kf_evnum = 0
456+ data_to_send = {"id" : self .kf_evnum ,
457+ "timestamp" : kf_start_timestamp ,
458+ "event" : "start_experiment" ,
459+ "value" : "0"
460+ }
461+ self ._kf_sendeventinfo (data_to_send )
462+
463+ # Update logfile text
464+ self .kf_triggers_history .append ({'kf_evnum' : '%s' % self .kf_evnum ,
465+ 'kf_start_timestamp' : kf_start_timestamp ,
466+ 'packet_sent' : data_to_send })
467+
468+
469+ def _kf_push_sample (self , timestamp , marker , marker_name ):
470+
471+ # Send trigger
472+ self .kf_evnum += 1
473+ kf_trigger_timestamp = int (time ()* 1e6 )
474+ data_to_send = {
475+ "id" : self .kf_evnum , #event_id,
476+ "timestamp" : kf_trigger_timestamp , # timestamp
477+ "event" : 'start_trial' , #marker_name, #event_name,
478+ "value" : str (marker ), #str(marker_name),
479+ }
480+ self ._kf_sendeventinfo (data_to_send )
481+
482+ # Update logfile text
483+ self .kf_triggers_history .append ({'kf_evnum' : '%s' % self .kf_evnum ,
484+ 'kf_trigger_timestamp' : kf_trigger_timestamp ,
485+ 'experiment_timestamp' : timestamp ,
486+ 'packet_sent' : data_to_send })
487+
488+
489+ def _stop_kf (self ):
490+
491+ self .kf_evnum += 1
492+ kf_stop_timestamp = int (time ()* 1e6 )
493+ data_to_send = {
494+ "id" : 3 , #self.kf_evnum,
495+ "timestamp" : kf_stop_timestamp ,
496+ "event" : "end_experiment" ,
497+ "value" : "5"
498+ }
499+ self ._kf_sendeventinfo (data_to_send )
500+
501+ self .kf_triggers_history .append ({'kf_evnum' : '%s' % self .kf_evnum ,
502+ 'kf_stop_timestamp' : kf_stop_timestamp ,
503+ 'packet_sent' : data_to_send })
504+
505+ if self .make_logfile :
506+ self .kf_logfile_handle .write (self .kf_triggers_history )
507+ self .kf_logfile_handle .close ()
508+
509+
510+ def _kf_sendeventinfo (self , event_info ):
511+
512+ event_info_pack = json .dumps (event_info ).encode ("utf-8" )
513+ msg = struct .pack ("!I" , len (event_info_pack )) + event_info_pack
514+ self .kf_socket .sendall (msg )
515+
516+
401517 #################################
402518 # Highlevel device functions #
403519 #################################
404-
520+
405521 def start (self , fn , duration = None ):
406522 """Starts the EEG device based on the defined backend.
407523
@@ -416,8 +532,10 @@ def start(self, fn, duration=None):
416532 self .markers = []
417533 elif self .backend == "muselsl" :
418534 self ._start_muse (duration )
535+ elif self .backend == "kernelflow" :
536+ self ._start_kf ()
419537
420- def push_sample (self , marker , timestamp ):
538+ def push_sample (self , marker , timestamp , marker_name = None ):
421539 """
422540 Universal method for pushing a marker and its timestamp to store alongside the EEG data.
423541
@@ -429,12 +547,16 @@ def push_sample(self, marker, timestamp):
429547 self ._brainflow_push_sample (marker = marker )
430548 elif self .backend == "muselsl" :
431549 self ._muse_push_sample (marker = marker , timestamp = timestamp )
550+ elif self .backend == "kernelflow" :
551+ self ._kf_push_sample (marker = marker ,timestamp = timestamp , marker_name = marker_name )
432552
433553 def stop (self ):
434554 if self .backend == "brainflow" :
435555 self ._stop_brainflow ()
436556 elif self .backend == "muselsl" :
437557 pass
558+ elif self .backend == "kernelflow" :
559+ self ._stop_kf ()
438560
439561 def get_recent (self , n_samples : int = 256 ):
440562 """
0 commit comments