-
Notifications
You must be signed in to change notification settings - Fork 115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
update query records and request to download api #204
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
from pydispatch import Dispatcher | ||
import warnings | ||
import threading | ||
import os | ||
|
||
|
||
# define request id | ||
|
@@ -34,6 +35,8 @@ | |
GET_CORTEX_INFO_ID = 22 | ||
UPDATE_MARKER_REQUEST_ID = 23 | ||
UNSUB_REQUEST_ID = 24 | ||
QUERY_RECORDS_ID = 25 | ||
REQUEST_DOWNLOAD_RECORDS_ID = 26 | ||
|
||
#define error_code | ||
ERR_PROFILE_ACCESS_DENIED = -32046 | ||
|
@@ -60,9 +63,9 @@ | |
|
||
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', | ||
'mc_training_threshold_done', 'create_record_done','query_records_done', 'request_download_records_done', 'stop_record_done','warn_cortex_stop_all_sub', | ||
'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'] | ||
|
@@ -73,6 +76,7 @@ def __init__(self, client_id, client_secret, debug_mode=False, **kwargs): | |
self.debug = debug_mode | ||
self.debit = 10 | ||
self.license = '' | ||
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.') | ||
|
@@ -92,6 +96,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" | ||
|
@@ -105,7 +111,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 = os.path.dirname(os.path.realpath(__file__)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should use high level package to handle file path |
||
parent_dir_path = os.path.abspath(os.path.join(file_dir_path, os.pardir)) | ||
certificate_path = os.path.join(parent_dir_path,'certificates', 'rootCA.pem') | ||
sslopt = {'ca_certs': certificate_path, "cert_reqs": ssl.CERT_REQUIRED} | ||
|
||
self.websock_thread = threading.Thread(target=self.ws.run_forever, args=(None, sslopt), name=threadName) | ||
self.websock_thread .start() | ||
|
@@ -160,8 +170,11 @@ def handle_result(self, recv_dic): | |
elif req_id == AUTHORIZE_ID: | ||
print("Authorize successfully.") | ||
self.auth = result_dic['cortexToken'] | ||
# query headsets | ||
self.query_headset() | ||
if self.auto_create_session: | ||
# query headsets | ||
self.query_headset() | ||
else: | ||
self.emit('authorize_done') | ||
elif req_id == QUERY_HEADSET_ID: | ||
self.headset_list = result_dic | ||
found_headset = False | ||
|
@@ -272,6 +285,14 @@ def handle_result(self, recv_dic): | |
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 == QUERY_RECORDS_ID: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why don't use switch case. it's more readable then a lot of if-else. |
||
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) | ||
elif req_id == REQUEST_DOWNLOAD_RECORDS_ID: | ||
self.emit('request_download_records_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']) | ||
|
@@ -666,6 +687,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 --------------------------------') | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
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(): | ||
|
||
# Please fill your application clientId and clientSecret before running script | ||
your_app_client_id = '' | ||
your_app_client_secret = '' | ||
|
||
# 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() | ||
|
||
# ----------------------------------------------------------- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should tell about optional params in readme file..