diff --git a/cpp-qt/cortexclient/Config.h b/cpp-qt/cortexclient/Config.h index f064dcc..ef0fdb8 100644 --- a/cpp-qt/cortexclient/Config.h +++ b/cpp-qt/cortexclient/Config.h @@ -18,9 +18,9 @@ along with this program. If not, see #include /* - * To get a client id and a client secret, you must connect to your Emotiv - * account on emotiv.com and create a Cortex app. - * https://www.emotiv.com/my-account/cortex-apps/ + * Enter your application Client ID and Client Secret below. + * You can obtain these credentials after registering your App ID with the Cortex SDK for development. + * For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app */ static const QString ClientId = "The client id of your Cortex app goes here"; static const QString ClientSecret = "The client secret of your Cortex app goes here"; diff --git a/csharp/CortexAccess/Config.cs b/csharp/CortexAccess/Config.cs index de71066..427882e 100644 --- a/csharp/CortexAccess/Config.cs +++ b/csharp/CortexAccess/Config.cs @@ -3,9 +3,9 @@ static class Config { /* - * To get a client id and a client secret, you must connect to your Emotiv - * account on emotiv.com and create a Cortex app. - * https://www.emotiv.com/my-account/cortex-apps/ + * Enter your application Client ID and Client Secret below. + * You can obtain these credentials after registering your App ID with the Cortex SDK for development. + * For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app */ public static string AppClientId = "put_your_application_client_id_here"; public static string AppClientSecret = "put_your_application_client_secret_here"; diff --git a/python/README.md b/python/README.md index e3bdab3..a9c904c 100644 --- a/python/README.md +++ b/python/README.md @@ -21,8 +21,7 @@ Before running the examples, please ensure you have completed the following step 3. **Obtain an EMOTIV Headset or Create a Virtual Device**: - Purchase a headset from the [EMOTIV online store](https://www.emotiv.com/), **or** - Use a virtual headset in the EMOTIV Launcher by following [these instructions](https://emotiv.gitbook.io/emotiv-launcher/devices-setting-up-virtual-brainwear-r/creating-a-virtual-brainwear-device). -4. **Get Client ID & Secret**: Log in to your Emotiv account at [emotiv.com](https://www.emotiv.com/my-account/cortex-apps/) and create a Cortex app. [Register here](https://id.emotivcloud.com/eoidc/account/registration/) if you don't have an account. -5. **Authorize Examples**: The first time you run these examples, you may need to grant permission for your application to work with Emotiv Cortex. +4. **Create your Cortex App**: Once you register your Cortex App ID, you will receive a Client ID and Client Secret, which serve as unique identifiers for your software application. For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app --- @@ -46,6 +45,7 @@ Demonstrates how to: - Create a new record - Stop a record - Export recorded data to CSV or EDF +- Query records and request to download record data See: [Records](https://emotiv.gitbook.io/cortex-api/records) ### 4. `marker.py` — Inject Markers diff --git a/python/cortex.py b/python/cortex.py index 27685bb..98c879f 100644 --- a/python/cortex.py +++ b/python/cortex.py @@ -26,6 +26,7 @@ import time import json from datetime import datetime +from pathlib import Path # define request id QUERY_HEADSET_ID = 1 @@ -53,6 +54,8 @@ UPDATE_MARKER_REQUEST_ID = 23 UNSUB_REQUEST_ID = 24 REFRESH_HEADSET_LIST_ID = 25 +QUERY_RECORDS_ID = 26 +REQUEST_DOWNLOAD_RECORDS_ID = 27 #define error_code ERR_PROFILE_ACCESS_DENIED = -32046 @@ -80,13 +83,31 @@ class Cortex(Dispatcher): - _events_ = ['inform_error','create_session_done', 'query_profile_done', 'load_unload_profile_done', + _events_ = ['inform_error', 'authorize_done', 'create_session_done', 'query_profile_done', 'load_unload_profile_done', 'save_profile_done', 'get_mc_active_action_done','mc_brainmap_done', 'mc_action_sensitivity_done', - 'mc_training_threshold_done', 'create_record_done', 'stop_record_done','warn_cortex_stop_all_sub', 'warn_record_post_processing_done', + 'mc_training_threshold_done', 'create_record_done', 'stop_record_done','warn_cortex_stop_all_sub', + 'warn_record_post_processing_done', 'query_records_done', 'request_download_records_done', 'inject_marker_done', 'update_marker_done', 'export_record_done', 'new_data_labels', 'new_com_data', 'new_fe_data', 'new_eeg_data', 'new_mot_data', 'new_dev_data', 'new_met_data', 'new_pow_data', 'new_sys_data'] def __init__(self, client_id, client_secret, debug_mode=False, **kwargs): + """ + Initialize a Cortex instance with authentication and configuration options. + Args: + client_id (str): The client ID for authentication. Must not be empty. + client_secret (str): The client secret for authentication. Must not be empty. + debug_mode (bool, optional): Enable debug mode. Defaults to False. + **kwargs: Additional configuration options. + license (str, optional): (Obsolete) This parameter is kept for backward compatibility. Always set to empty string + debit (int, optional): Debit value for usage. + headset_id (str, optional): ID of the headset to connect. + auto_create_session (bool, optional): Automatically create session if True. For export and query records, don't need to create session. + Raises: + ValueError: If client_id or client_secret is empty. + Description: + This constructor sets up the Cortex instance with required authentication credentials and optional configuration parameters. + It validates the presence of client_id and client_secret, and allows further customization via keyword arguments. + """ self.session_id = '' self.headset_id = '' @@ -94,6 +115,7 @@ def __init__(self, client_id, client_secret, debug_mode=False, **kwargs): self.debit = 10 self.license = '' self.isHeadsetConnected = False + self.auto_create_session = True if client_id == '': raise ValueError('Empty your_app_client_id. Please fill in your_app_client_id before running the example.') @@ -113,6 +135,8 @@ def __init__(self, client_id, client_secret, debug_mode=False, **kwargs): self.debit = value elif key == 'headset_id': self.headset_id = value + elif key == 'auto_create_session': + self.auto_create_session = value def open(self): url = "wss://localhost:6868" @@ -126,7 +150,11 @@ def open(self): # As default, a Emotiv self-signed certificate is required. # If you don't want to use the certificate, please replace by the below line by sslopt={"cert_reqs": ssl.CERT_NONE} - sslopt = {'ca_certs': "../certificates/rootCA.pem", "cert_reqs": ssl.CERT_REQUIRED} + file_dir_path = Path(__file__).resolve().parent + parent_dir_path = file_dir_path.parent + + certificate_path = Path(parent_dir_path, 'certificates', 'rootCA.pem') + sslopt = {'ca_certs': str(certificate_path), "cert_reqs": ssl.CERT_REQUIRED} self.websock_thread = threading.Thread(target=self.ws.run_forever, args=(None, sslopt), name=thread_name) self.websock_thread .start() @@ -160,173 +188,237 @@ def handle_result(self, recv_dic): req_id = recv_dic['id'] result_dic = recv_dic['result'] - if req_id == HAS_ACCESS_RIGHT_ID: - access_granted = result_dic['accessGranted'] - if access_granted == True: - # authorize - self.authorize() - else: - # request access - self.request_access() - elif req_id == REQUEST_ACCESS_ID: - access_granted = result_dic['accessGranted'] - - if access_granted == True: - # authorize - self.authorize() - else: - # wait approve from Emotiv Launcher - msg = result_dic['message'] - warnings.warn(msg) - elif req_id == AUTHORIZE_ID: - print("Authorize successfully.") - self.auth = result_dic['cortexToken'] + # Dictionary dispatch pattern for better readability and performance + handler = self._get_result_handler(req_id) + if handler: + handler(result_dic) + else: + print('No handling for response of request ' + str(req_id)) + + def _get_result_handler(self, req_id): + """Return the appropriate handler function for the given request ID.""" + handlers = { + HAS_ACCESS_RIGHT_ID: self._handle_has_access_right, + REQUEST_ACCESS_ID: self._handle_request_access, + AUTHORIZE_ID: self._handle_authorize, + QUERY_HEADSET_ID: self._handle_query_headset, + CREATE_SESSION_ID: self._handle_create_session, + SUB_REQUEST_ID: self._handle_sub_request, + UNSUB_REQUEST_ID: self._handle_unsub_request, + QUERY_PROFILE_ID: self._handle_query_profile, + SETUP_PROFILE_ID: self._handle_setup_profile, + GET_CURRENT_PROFILE_ID: self._handle_get_current_profile, + DISCONNECT_HEADSET_ID: self._handle_disconnect_headset, + MENTAL_COMMAND_ACTIVE_ACTION_ID: self._handle_mental_command_active_action, + MENTAL_COMMAND_TRAINING_THRESHOLD: self._handle_mental_command_training_threshold, + MENTAL_COMMAND_BRAIN_MAP_ID: self._handle_mental_command_brain_map, + SENSITIVITY_REQUEST_ID: self._handle_sensitivity_request, + QUERY_RECORDS_ID: self._handle_query_records, + REQUEST_DOWNLOAD_RECORDS_ID: self._handle_request_download_records, + CREATE_RECORD_REQUEST_ID: self._handle_create_record_request, + STOP_RECORD_REQUEST_ID: self._handle_stop_record_request, + EXPORT_RECORD_ID: self._handle_export_record, + INJECT_MARKER_REQUEST_ID: self._handle_inject_marker_request, + UPDATE_MARKER_REQUEST_ID: self._handle_update_marker_request, + } + return handlers.get(req_id) + + def _handle_has_access_right(self, result_dic): + access_granted = result_dic['accessGranted'] + if access_granted == True: + # authorize + self.authorize() + else: + # request access + self.request_access() + + def _handle_request_access(self, result_dic): + access_granted = result_dic['accessGranted'] + + if access_granted == True: + # authorize + self.authorize() + else: + # wait approve from Emotiv Launcher + msg = result_dic['message'] + warnings.warn(msg) + + def _handle_authorize(self, result_dic): + print("Authorize successfully.") + self.auth = result_dic['cortexToken'] + if self.auto_create_session: #After successful authorization, the app will call the API refresh headset list for the first time self.refresh_headset_list() # query headsets self.query_headset() - elif req_id == QUERY_HEADSET_ID: - self.headset_list = result_dic - found_headset = False - headset_status = '' - for ele in self.headset_list: - hs_id = ele['id'] - status = ele['status'] - connected_by = ele['connectedBy'] - print('headsetId: {0}, status: {1}, connected_by: {2}'.format(hs_id, status, connected_by)) - if self.headset_id != '' and self.headset_id == hs_id: - found_headset = True - headset_status = status - - if len(self.headset_list) == 0: - self.isHeadsetConnected = False - warnings.warn("No headset available. Please turn on a headset.") - elif self.headset_id == '': - # set first headset is default headset - self.headset_id = self.headset_list[0]['id'] - # call query headet again + else: + self.emit('authorize_done') + + def _handle_query_headset(self, result_dic): + self.headset_list = result_dic + found_headset = False + headset_status = '' + for ele in self.headset_list: + hs_id = ele['id'] + status = ele['status'] + connected_by = ele['connectedBy'] + print('headsetId: {0}, status: {1}, connected_by: {2}'.format(hs_id, status, connected_by)) + if self.headset_id != '' and self.headset_id == hs_id: + found_headset = True + headset_status = status + + if len(self.headset_list) == 0: + self.isHeadsetConnected = False + warnings.warn("No headset available. Please turn on a headset.") + elif self.headset_id == '': + # set first headset is default headset + self.headset_id = self.headset_list[0]['id'] + # call query headet again + self.query_headset() + elif found_headset == False: + warnings.warn("Can not found the headset " + self.headset_id + ". Please make sure the id is correct.") + elif found_headset == True: + if headset_status == 'connected': + self.isHeadsetConnected = True + # create session with the headset + self.create_session() + elif headset_status == 'discovered': + self.connect_headset(self.headset_id) + elif headset_status == 'connecting': + # wait 3 seconds and query headset again + time.sleep(3) self.query_headset() - elif found_headset == False: - warnings.warn("Can not found the headset " + self.headset_id + ". Please make sure the id is correct.") - elif found_headset == True: - if headset_status == 'connected': - self.isHeadsetConnected = True - # create session with the headset - self.create_session() - elif headset_status == 'discovered': - self.connect_headset(self.headset_id) - elif headset_status == 'connecting': - # wait 3 seconds and query headset again - time.sleep(3) - self.query_headset() - else: - warnings.warn('query_headset resp: Invalid connection status ' + headset_status) - elif req_id == CREATE_SESSION_ID: - self.session_id = result_dic['id'] - print("The session " + self.session_id + " is created successfully.") - self.emit('create_session_done', data=self.session_id) - elif req_id == SUB_REQUEST_ID: - # handle data label - for stream in result_dic['success']: - stream_name = stream['streamName'] - stream_labels = stream['cols'] - print('The data stream '+ stream_name + ' is subscribed successfully.') - # ignore com, fac and sys data label because they are handled in on_new_data - if stream_name != 'com' and stream_name != 'fac': - self.extract_data_labels(stream_name, stream_labels) - - for stream in result_dic['failure']: - stream_name = stream['streamName'] - stream_msg = stream['message'] - print('The data stream '+ stream_name + ' is subscribed unsuccessfully. Because: ' + stream_msg) - elif req_id == UNSUB_REQUEST_ID: - for stream in result_dic['success']: - stream_name = stream['streamName'] - print('The data stream '+ stream_name + ' is unsubscribed successfully.') - - for stream in result_dic['failure']: - stream_name = stream['streamName'] - stream_msg = stream['message'] - print('The data stream '+ stream_name + ' is unsubscribed unsuccessfully. Because: ' + stream_msg) - - elif req_id == QUERY_PROFILE_ID: - profile_list = [] - for ele in result_dic: - if 'name' in ele: - profile_name = str(ele['name']) - read_only = ele['readOnly'] - print('profile name :', profile_name, " readonly :", read_only) - profile_list.append(profile_name) - else: - print('Result does not contain name field.') - - self.emit('query_profile_done', data=profile_list) - elif req_id == SETUP_PROFILE_ID: - action = result_dic['action'] - if action == 'create': - profile_name = result_dic['name'] - if profile_name == self.profile_name: - # load profile - self.setup_profile(profile_name, 'load') - elif action == 'load': - print('load profile successfully') - self.emit('load_unload_profile_done', isLoaded=True) - elif action == 'unload': - self.emit('load_unload_profile_done', isLoaded=False) - elif action == 'save': - self.emit('save_profile_done') - elif req_id == GET_CURRENT_PROFILE_ID: - print(result_dic) - name = result_dic['name'] - if name is None: - # no profile loaded with the headset - print('get_current_profile: no profile loaded with the headset ' + self.headset_id) - self.setup_profile(self.profile_name, 'load') else: - loaded_by_this_app = result_dic['loadedByThisApp'] - print('get current profile rsp: ' + name + ", loadedByThisApp: " + str(loaded_by_this_app)) - if name != self.profile_name: - warnings.warn("There is profile " + name + " is loaded for headset " + self.headset_id) - elif loaded_by_this_app == True: - self.emit('load_unload_profile_done', isLoaded=True) - else: - self.setup_profile(self.profile_name, 'unload') - # warnings.warn("The profile " + name + " is loaded by other applications") - elif req_id == DISCONNECT_HEADSET_ID: - print("Disconnect headset " + self.headset_id) - self.headset_id = '' - elif req_id == MENTAL_COMMAND_ACTIVE_ACTION_ID: - self.emit('get_mc_active_action_done', data=result_dic) - elif req_id == MENTAL_COMMAND_TRAINING_THRESHOLD: - self.emit('mc_training_threshold_done', data=result_dic) - elif req_id == MENTAL_COMMAND_BRAIN_MAP_ID: - self.emit('mc_brainmap_done', data=result_dic) - elif req_id == SENSITIVITY_REQUEST_ID: - self.emit('mc_action_sensitivity_done', data=result_dic) - elif req_id == CREATE_RECORD_REQUEST_ID: - self.record_id = result_dic['record']['uuid'] - self.emit('create_record_done', data=result_dic['record']) - elif req_id == STOP_RECORD_REQUEST_ID: - self.emit('stop_record_done', data=result_dic['record']) - elif req_id == EXPORT_RECORD_ID: - # handle data lable - success_export = [] - for record in result_dic['success']: - record_id = record['recordId'] - success_export.append(record_id) - - for record in result_dic['failure']: - record_id = record['recordId'] - failure_msg = record['message'] - print('export_record resp failure cases: '+ record_id + ":" + failure_msg) - - self.emit('export_record_done', data=success_export) - elif req_id == INJECT_MARKER_REQUEST_ID: - self.emit('inject_marker_done', data=result_dic['marker']) - elif req_id == UPDATE_MARKER_REQUEST_ID: - self.emit('update_marker_done', data=result_dic['marker']) + warnings.warn('query_headset resp: Invalid connection status ' + headset_status) + + def _handle_create_session(self, result_dic): + self.session_id = result_dic['id'] + print("The session " + self.session_id + " is created successfully.") + self.emit('create_session_done', data=self.session_id) + + def _handle_sub_request(self, result_dic): + # handle data label + for stream in result_dic['success']: + stream_name = stream['streamName'] + stream_labels = stream['cols'] + print('The data stream '+ stream_name + ' is subscribed successfully.') + # ignore com, fac and sys data label because they are handled in on_new_data + if stream_name != 'com' and stream_name != 'fac': + self.extract_data_labels(stream_name, stream_labels) + + for stream in result_dic['failure']: + stream_name = stream['streamName'] + stream_msg = stream['message'] + print('The data stream '+ stream_name + ' is subscribed unsuccessfully. Because: ' + stream_msg) + + def _handle_unsub_request(self, result_dic): + for stream in result_dic['success']: + stream_name = stream['streamName'] + print('The data stream '+ stream_name + ' is unsubscribed successfully.') + + for stream in result_dic['failure']: + stream_name = stream['streamName'] + stream_msg = stream['message'] + print('The data stream '+ stream_name + ' is unsubscribed unsuccessfully. Because: ' + stream_msg) + + def _handle_query_profile(self, result_dic): + profile_list = [] + for ele in result_dic: + if 'name' in ele: + profile_name = str(ele['name']) + read_only = ele['readOnly'] + print('profile name :', profile_name, " readonly :", read_only) + profile_list.append(profile_name) + else: + print('Result does not contain name field.') + + self.emit('query_profile_done', data=profile_list) + + def _handle_setup_profile(self, result_dic): + action = result_dic['action'] + if action == 'create': + profile_name = result_dic['name'] + if profile_name == self.profile_name: + # load profile + self.setup_profile(profile_name, 'load') + elif action == 'load': + print('load profile successfully') + self.emit('load_unload_profile_done', isLoaded=True) + elif action == 'unload': + self.emit('load_unload_profile_done', isLoaded=False) + elif action == 'save': + self.emit('save_profile_done') + + def _handle_get_current_profile(self, result_dic): + print(result_dic) + name = result_dic['name'] + if name is None: + # no profile loaded with the headset + print('get_current_profile: no profile loaded with the headset ' + self.headset_id) + self.setup_profile(self.profile_name, 'load') else: - print('No handling for response of request ' + str(req_id)) + loaded_by_this_app = result_dic['loadedByThisApp'] + print('get current profile rsp: ' + name + ", loadedByThisApp: " + str(loaded_by_this_app)) + if name != self.profile_name: + warnings.warn("There is profile " + name + " is loaded for headset " + self.headset_id) + elif loaded_by_this_app == True: + self.emit('load_unload_profile_done', isLoaded=True) + else: + self.setup_profile(self.profile_name, 'unload') + # warnings.warn("The profile " + name + " is loaded by other applications") + + def _handle_disconnect_headset(self, result_dic): + print("Disconnect headset " + self.headset_id) + self.headset_id = '' + + def _handle_mental_command_active_action(self, result_dic): + self.emit('get_mc_active_action_done', data=result_dic) + + def _handle_mental_command_training_threshold(self, result_dic): + self.emit('mc_training_threshold_done', data=result_dic) + + def _handle_mental_command_brain_map(self, result_dic): + self.emit('mc_brainmap_done', data=result_dic) + + def _handle_sensitivity_request(self, result_dic): + self.emit('mc_action_sensitivity_done', data=result_dic) + + def _handle_query_records(self, result_dic): + record_num = result_dic['count'] + limit_num = result_dic['limit'] + offset_num = result_dic['offset'] + records = result_dic['records'] + self.emit('query_records_done', data=records, count=record_num, limit=limit_num, offset=offset_num) + + def _handle_request_download_records(self, result_dic): + self.emit('request_download_records_done', data=result_dic) + + def _handle_create_record_request(self, result_dic): + self.record_id = result_dic['record']['uuid'] + self.emit('create_record_done', data=result_dic['record']) + + def _handle_stop_record_request(self, result_dic): + self.emit('stop_record_done', data=result_dic['record']) + + def _handle_export_record(self, result_dic): + # handle data lable + success_export = [] + for record in result_dic['success']: + record_id = record['recordId'] + success_export.append(record_id) + + for record in result_dic['failure']: + record_id = record['recordId'] + failure_msg = record['message'] + print('export_record resp failure cases: '+ record_id + ":" + failure_msg) + + self.emit('export_record_done', data=success_export) + + def _handle_inject_marker_request(self, result_dic): + self.emit('inject_marker_done', data=result_dic['marker']) + + def _handle_update_marker_request(self, result_dic): + self.emit('update_marker_done', data=result_dic['marker']) def handle_error(self, recv_dic): req_id = recv_dic['id'] @@ -334,33 +426,57 @@ def handle_error(self, recv_dic): self.emit('inform_error', error_data=recv_dic['error']) def handle_warning(self, warning_dic): - if self.debug: print(warning_dic) + warning_code = warning_dic['code'] warning_msg = warning_dic['message'] - if warning_code == ACCESS_RIGHT_GRANTED: - # call authorize again - self.authorize() - elif warning_code == HEADSET_CONNECTED: - # query headset again then create session - self.query_headset() - elif warning_code == CORTEX_AUTO_UNLOAD_PROFILE: - self.profile_name = '' - elif warning_code == CORTEX_STOP_ALL_STREAMS: - # print(warning_msg['behavior']) - session_id = warning_msg['sessionId'] - if session_id == self.session_id: - self.emit('warn_cortex_stop_all_sub', data=session_id) - self.session_id = '' - elif warning_code == CORTEX_RECORD_POST_PROCESSING_DONE: - record_id = warning_msg['recordId'] - self.emit('warn_record_post_processing_done', data=record_id) - elif warning_code == HEADSET_SCANNING_FINISHED: - # After headset scanning finishes, if no headset is connected yet, the app should call the controlDevice("refresh") again - # We recommend the app should NOT call controlDevice("refresh") when a headset is connected, to have the best data stream quality. - if (self.isHeadsetConnected == False): - self.refresh_headset_list() + + # Dictionary dispatch pattern for better readability and performance + handler = self._get_warning_handler(warning_code) + if handler: + handler(warning_msg) + # Note: No else clause needed as some warning codes may not require handling + + def _get_warning_handler(self, warning_code): + """Return the appropriate handler function for the given warning code.""" + handlers = { + ACCESS_RIGHT_GRANTED: self._handle_access_right_granted, + HEADSET_CONNECTED: self._handle_headset_connected, + CORTEX_AUTO_UNLOAD_PROFILE: self._handle_cortex_auto_unload_profile, + CORTEX_STOP_ALL_STREAMS: self._handle_cortex_stop_all_streams, + CORTEX_RECORD_POST_PROCESSING_DONE: self._handle_cortex_record_post_processing_done, + HEADSET_SCANNING_FINISHED: self._handle_headset_scanning_finished, + } + return handlers.get(warning_code) + + def _handle_access_right_granted(self, warning_msg): + # call authorize again + self.authorize() + + def _handle_headset_connected(self, warning_msg): + # query headset again then create session + self.query_headset() + + def _handle_cortex_auto_unload_profile(self, warning_msg): + self.profile_name = '' + + def _handle_cortex_stop_all_streams(self, warning_msg): + # print(warning_msg['behavior']) + session_id = warning_msg['sessionId'] + if session_id == self.session_id: + self.emit('warn_cortex_stop_all_sub', data=session_id) + self.session_id = '' + + def _handle_cortex_record_post_processing_done(self, warning_msg): + record_id = warning_msg['recordId'] + self.emit('warn_record_post_processing_done', data=record_id) + + def _handle_headset_scanning_finished(self, warning_msg): + # After headset scanning finishes, if no headset is connected yet, the app should call the controlDevice("refresh") again + # We recommend the app should NOT call controlDevice("refresh") when a headset is connected, to have the best data stream quality. + if (self.isHeadsetConnected == False): + self.refresh_headset_list() def handle_stream_data(self, result_dic): if result_dic.get('com') != None: @@ -705,6 +821,41 @@ def train_request(self, detection, action, status): self.ws.send(json.dumps(train_request_json)) + def query_records(self, query_params): + print('query records --------------------------------') + + params_val = {"cortexToken": self.auth} + + for key, value in query_params.items(): + params_val.update({key: value}) + + query_records_request = { + "jsonrpc": "2.0", + "method": "queryRecords", + "params": params_val, + "id": QUERY_RECORDS_ID + } + if self.debug: + print('query records request:\n', json.dumps(query_records_request, indent=4)) + + self.ws.send(json.dumps(query_records_request)) + + def request_download_records(self, recordIds): + print('request to download records --------------------------------') + + params_val = {"cortexToken": self.auth, 'recordIds': recordIds} + + download_records_request = { + "jsonrpc": "2.0", + "method": "requestToDownloadRecordData", + "params": params_val, + "id": REQUEST_DOWNLOAD_RECORDS_ID + } + if self.debug: + print('requestToDownloadRecordData request:\n', json.dumps(download_records_request, indent=4)) + + self.ws.send(json.dumps(download_records_request)) + def create_record(self, title, **kwargs): print('create record --------------------------------') diff --git a/python/facial_expression_train.py b/python/facial_expression_train.py index 61b3e18..fc881df 100644 --- a/python/facial_expression_train.py +++ b/python/facial_expression_train.py @@ -258,9 +258,11 @@ def on_inform_error(self, *args, **kwargs): def main(): - # Please fill your application clientId and clientSecret before running script - your_app_client_id = '' - your_app_client_secret = '' + # Enter your application Client ID and Client Secret below. + # You can obtain these credentials after registering your App ID with the Cortex SDK for development. + # For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app + your_app_client_id = 'put_your_app_client_id_here' + your_app_client_secret = 'put_your_app_client_secret_here' # Init Train t=Train(your_app_client_id, your_app_client_secret) diff --git a/python/live_advance.py b/python/live_advance.py index 69eab76..c1b4c1a 100644 --- a/python/live_advance.py +++ b/python/live_advance.py @@ -275,9 +275,11 @@ def on_inform_error(self, *args, **kwargs): def main(): - # Please fill your application clientId and clientSecret before running script - your_app_client_id = '' - your_app_client_secret = '' + # Enter your application Client ID and Client Secret below. + # You can obtain these credentials after registering your App ID with the Cortex SDK for development. + # For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app + your_app_client_id = 'put_your_app_client_id_here' + your_app_client_secret = 'put_your_app_client_secret_here' # Init live advance l = LiveAdvance(your_app_client_id, your_app_client_secret) diff --git a/python/marker.py b/python/marker.py index a6b535c..1c75355 100644 --- a/python/marker.py +++ b/python/marker.py @@ -174,7 +174,6 @@ def on_warn_record_post_processing_done(self, *args, **kwargs): # - Please reference to https://emotiv.gitbook.io/cortex-api/ first. # - Connect your headset with dongle or bluetooth. You can see the headset via Emotiv Launcher # - Please make sure the your_app_client_id and your_app_client_secret are set before starting running. -# - In the case you borrow license from others, you need to add license = "xxx-yyy-zzz" as init parameter # - Check the on_create_session_done() to see a record is created. # RESULT # - record data then inject marker each 3 seconds @@ -184,9 +183,11 @@ def on_warn_record_post_processing_done(self, *args, **kwargs): def main(): - # Please fill your application clientId and clientSecret before running script - your_app_client_id = '' - your_app_client_secret = '' + # Enter your application Client ID and Client Secret below. + # You can obtain these credentials after registering your App ID with the Cortex SDK for development. + # For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app + your_app_client_id = 'put_your_app_client_id_here' + your_app_client_secret = 'put_your_app_client_secret_here' m = Marker(your_app_client_id, your_app_client_secret) diff --git a/python/mental_command_train.py b/python/mental_command_train.py index 728445e..0b4f986 100644 --- a/python/mental_command_train.py +++ b/python/mental_command_train.py @@ -269,9 +269,11 @@ def on_inform_error(self, *args, **kwargs): def main(): - # Please fill your application clientId and clientSecret before running script - your_app_client_id = '' - your_app_client_secret = '' + # Enter your application Client ID and Client Secret below. + # You can obtain these credentials after registering your App ID with the Cortex SDK for development. + # For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app + your_app_client_id = 'put_your_app_client_id_here' + your_app_client_secret = 'put_your_app_client_secret_here' # Init Train t=Train(your_app_client_id, your_app_client_secret) diff --git a/python/query_records.py b/python/query_records.py new file mode 100644 index 0000000..15aaf13 --- /dev/null +++ b/python/query_records.py @@ -0,0 +1,205 @@ +from cortex import Cortex + +class Records(): + """ + A class to query records, request to download undownloaded records, and export local records + + Attributes + ---------- + c : Cortex + Cortex communicate with Emotiv Cortex Service + + Methods + ------- + start(): + start authorize process. + query_records(): + To query records owned by the current user + request_download_records() + To request to download records if they are not at local machine + export_record() + To export records to CSV/ EDF files + """ + def __init__(self, app_client_id, app_client_secret, **kwargs): + """ + Constructs cortex client and bind a function to query records, request to download and export records + If you do not want to log request and response message , set debug_mode = False. The default is True + """ + print("Query Records __init__") + self.c = Cortex(app_client_id, app_client_secret, debug_mode=True, **kwargs) + self.c.bind(authorize_done=self.on_authorize_done) + self.c.bind(query_records_done=self.on_query_records_done) + self.c.bind(export_record_done=self.on_export_record_done) + self.c.bind(request_download_records_done=self.on_request_download_records_done) + self.c.bind(inform_error=self.on_inform_error) + + def start(self): + """ + To open websocket and process authorizing step. + After get authorize_done The program will query records + Returns + ------- + None + """ + self.c.open() + + def query_records(self, license_id, application_id): + """ + To query records + Parameters + ---------- + orderBy : array of object, required : Specify how to sort the records. + query: object, required: An object to filter the records. + If you set an empty object, it will return all records created by your application + If you want get records created by other application, you need set licenseId and applicationId as parameter of query object + More detail at https://emotiv.gitbook.io/cortex-api/records/queryrecords + Returns + ------- + """ + + query_obj = {} + if license_id != '': + query_obj["licenseId"] = license_id + if application_id != '': + query_obj["applicationId"] = application_id + + query_params = { + "orderBy": [{ "startDatetime": "DESC" }], + "query": query_obj, + "includeSyncStatusInfo":True + } + self.c.query_records(query_params) + + def request_download_records(self, record_ids): + """ + To request to download records + Parameters + ---------- + record_ids : list, required: list of wanted record ids + More detail at https://emotiv.gitbook.io/cortex-api/records/requesttodownloadrecorddata + Returns + ------- + None + """ + self.c.request_download_records(record_ids) + + def export_record(self, record_ids, license_ids): + """ + To export records + By default, you can only export the records that were created by your application. + If you want to export a record that was created by another applications + then you must provide the license ids of those applications in the parameter licenseIds. + Parameters + record_ids: list, required: list of wanted export record ids + license_ids: list, no required: list of license id of other applications + ---------- + More detail at https://emotiv.gitbook.io/cortex-api/records/exportrecord + Returns + ------- + None + """ + folder = '' # your place to export, you should have write permission, for example: 'C:\\Users\\NTT\\Desktop' + stream_types = ['EEG', 'MOTION', 'PM', 'BP'] + export_format = 'CSV' # support 'CSV' or 'EDF' + version = 'V2' + self.c.export_record(folder, stream_types, export_format, record_ids, version, licenseIds=license_ids) + + + # callbacks functions + def on_authorize_done(self, *args, **kwargs): + print('on_authorize_done') + # query records + self.query_records(self.license_id, self.application_id) + + # callbacks functions + def on_query_records_done(self, *args, **kwargs): + data = kwargs.get('data') + count = kwargs.get('count') + print('on_query_records_done: total records are {0}'.format(count)) + # print(data) + not_downloaded_record_Ids = [] + export_record_Ids = [] + license_ids = [] + for item in data: + uuid = item['uuid'] + sync_status = item["syncStatus"]["status"] + application_id = item["applicationId"] + license_id = item["licenseId"] + print("recordId {0}, applicationId {1}, sync status {2}".format(uuid, application_id, sync_status)) + if (sync_status == "notDownloaded") : + not_downloaded_record_Ids.append(uuid) + elif (sync_status == "neverUploaded") or (sync_status == "downloaded"): + export_record_Ids.append(uuid) + if license_id not in license_ids: + license_ids.append(license_id) + + # download records has not downloaded to local machine + if len(not_downloaded_record_Ids) > 0: + self.request_download_records(not_downloaded_record_Ids) + + # Open comment below to export records + # if len(export_record_Ids) > 0: # or export records are in local machine + # self.export_record(export_record_Ids, license_ids) + + def on_export_record_done(self, *args, **kwargs): + print('on_export_record_done: the successful record exporting as below:') + data = kwargs.get('data') + print(data) + self.c.close() + + def on_request_download_records_done(self, *args, **kwargs): + data = kwargs.get('data') + success_records = [] + for item in data['success']: + record_Id = item['recordId'] + print('The record '+ record_Id + ' is downloaded successfully.') + success_records.append(record_Id) + + for item in data['failure']: + record_Id = item['recordId'] + failed_msg = item['message'] + print('The record '+ record_Id + ' is downloaded unsuccessfully. Because: ' + failed_msg) + + self.c.close() + + def on_inform_error(self, *args, **kwargs): + error_data = kwargs.get('error_data') + print(error_data) + +# ----------------------------------------------------------- +# +# GETTING STARTED +# - Please reference to https://emotiv.gitbook.io/cortex-api/ first. +# - Please make sure the your_app_client_id and your_app_client_secret are set before starting running. +# RESULT +# - on_query_records_done: will show all records filtered by query condition in query_record +# - on_export_record_done: will show the successful exported record +# - on_request_download_records_done: will show all success and failure download case +# +# ----------------------------------------------------------- + +def main(): + + # Enter your application Client ID and Client Secret below. + # You can obtain these credentials after registering your App ID with the Cortex SDK for development. + # For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app + your_app_client_id = 'put_your_app_client_id_here' + your_app_client_secret = 'put_your_app_client_secret_here' + + # Don't need to create session in this case + r = Records(your_app_client_id, your_app_client_secret, auto_create_session= False) + + # As default, the Program will query records of your application. + # In the case, you want to query records created from other application (such as EmotivPRO). + # You need set license_id and application_id of the application + # If set license_id without application_id. It will return records created from all applications use the license_id + # If set both license_id and application_id. It will return records from the application has the application_id + r.license_id = '' + r.application_id = '' + + r.start() + +if __name__ =='__main__': + main() + +# ----------------------------------------------------------- diff --git a/python/record.py b/python/record.py index 6f3c611..6060186 100644 --- a/python/record.py +++ b/python/record.py @@ -134,7 +134,6 @@ def on_inform_error(self, *args, **kwargs): # - Please reference to https://emotiv.gitbook.io/cortex-api/ first. # - Connect your headset with dongle or bluetooth. You can see the headset via Emotiv Launcher # - Please make sure the your_app_client_id and your_app_client_secret are set before starting running. -# - In the case you borrow license from others, you need to add license = "xxx-yyy-zzz" as init parameter # - Check the on_create_session_done() to see how to create a record. # - Check the on_warn_cortex_stop_all_sub() to see how to export record # RESULT @@ -147,9 +146,11 @@ def on_inform_error(self, *args, **kwargs): def main(): - # Please fill your application clientId and clientSecret before running script - your_app_client_id = '' - your_app_client_secret = '' + # Enter your application Client ID and Client Secret below. + # You can obtain these credentials after registering your App ID with the Cortex SDK for development. + # For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app + your_app_client_id = 'put_your_app_client_id_here' + your_app_client_secret = 'put_your_app_client_secret_here' r = Record(your_app_client_id, your_app_client_secret) diff --git a/python/sub_data.py b/python/sub_data.py index fba3347..181ec25 100644 --- a/python/sub_data.py +++ b/python/sub_data.py @@ -217,7 +217,6 @@ def on_inform_error(self, *args, **kwargs): # - Please reference to https://emotiv.gitbook.io/cortex-api/ first. # - Connect your headset with dongle or bluetooth. You can see the headset via Emotiv Launcher # - Please make sure the your_app_client_id and your_app_client_secret are set before starting running. -# - In the case you borrow license from others, you need to add license = "xxx-yyy-zzz" as init parameter # RESULT # - the data labels will be retrieved at on_new_data_labels # - the data will be retreived at on_new_[dataStream]_data @@ -226,9 +225,11 @@ def on_inform_error(self, *args, **kwargs): def main(): - # Please fill your application clientId and clientSecret before running script - your_app_client_id = '' - your_app_client_secret = '' + # Enter your application Client ID and Client Secret below. + # You can obtain these credentials after registering your App ID with the Cortex SDK for development. + # For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app + your_app_client_id = 'put_your_app_client_id_here' + your_app_client_secret = 'put_your_app_client_secret_here' s = Subcribe(your_app_client_id, your_app_client_secret) diff --git a/unity/Assets/Plugins/AppConfig.cs b/unity/Assets/Plugins/AppConfig.cs index 2a8914c..fc533d4 100644 --- a/unity/Assets/Plugins/AppConfig.cs +++ b/unity/Assets/Plugins/AppConfig.cs @@ -3,18 +3,24 @@ /// public static class AppConfig { - public static string AppVersion = "1.1.0"; - public static string AppName = "UnityApp"; - public static string ClientId = "put_your_client_id_here"; - public static string ClientSecret = "put_your_client_secret_here"; - public static bool IsDataBufferUsing = false; // Set false if you want to display data directly to MessageLog without storing in Data Buffer - public static bool AllowSaveLogToFile = false; // Set true to save log to file and cortex token to local file for next time use - + /* + * Enter your application Client ID and Client Secret below. + * You can obtain these credentials after registering your App ID with the Cortex SDK for development. + * For instructions, visit: https://emotiv.gitbook.io/cortex-api#create-a-cortex-app + */ + public static string ClientId = "put_your_client_id_here"; + public static string ClientSecret = "put_your_client_secret_here"; + + public static string AppVersion = "1.1.0"; + public static string AppName = "UnityApp"; + public static bool IsDataBufferUsing = false; // Set false if you want to display data directly to MessageLog without storing in Data Buffer + public static bool AllowSaveLogToFile = false; // Set true to save log to file and cortex token to local file for next time use + #if !USE_EMBEDDED_LIB && !UNITY_ANDROID && !UNITY_IOS // only for desktop without embedded cortex - public static string AppUrl = "wss://localhost:6868"; // for desktop without embedded cortex + public static string AppUrl = "wss://localhost:6868"; // for desktop without embedded cortex #else - public static string AppUrl = ""; // Don't need AppUrl for mobile and embedded cortex + public static string AppUrl = ""; // Don't need AppUrl for mobile and embedded cortex #endif } \ No newline at end of file diff --git a/unity/Assets/Plugins/Emotiv-Unity-Plugin b/unity/Assets/Plugins/Emotiv-Unity-Plugin index a6334de..245f4e0 160000 --- a/unity/Assets/Plugins/Emotiv-Unity-Plugin +++ b/unity/Assets/Plugins/Emotiv-Unity-Plugin @@ -1 +1 @@ -Subproject commit a6334def9755dc884c0417aed6e2a03796cbb88c +Subproject commit 245f4e0b1a75c19fb70cc3d93846152fb1d5fb33 diff --git a/unity/Assets/SimpleExample.cs b/unity/Assets/SimpleExample.cs index 32baf9a..53ed419 100644 --- a/unity/Assets/SimpleExample.cs +++ b/unity/Assets/SimpleExample.cs @@ -292,9 +292,14 @@ public void onExportRecordBtnClick() // Determine the folder path for export. Default to Desktop folder (or persistent data path on mobile) string folderPath; - #if UNITY_ANDROID || UNITY_IOS + #if UNITY_ANDROID // On mobile, use persistent data path folderPath = Application.persistentDataPath; + #elif UNITY_IOS + // On iOS do not need to specify the folder. + // Cortex exports the data to the "Documents" folder of the current application. + // Empty path lets the plugin handle default Documents folder location + folderPath = ""; #else // On desktop, use Desktop folder folderPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);