From 0843a39e5b4af8c5be2d0b2991e65063ea5bd472 Mon Sep 17 00:00:00 2001 From: James Bruska Date: Mon, 16 Oct 2023 15:28:08 -0700 Subject: [PATCH 1/2] Fixed cmake --- CMakeLists.txt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cb031cee..d60d3f4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -351,12 +351,16 @@ else (HDF5_EXPORT) set (ELEMEM_DEFINES ${ELEMEM_DEFINES} NO_HDF5) endif (HDF5_EXPORT) -find_library (CBSDK_LIB "cbsdk" PATHS ${PROJECT_SOURCE_DIR}/lib64) -if (NOT CBSDK_LIB) - message (FATAL_ERROR - "cbsdk library missing from ${PROJECT_SOURCE_DIR}/lib64" - ) -endif () +if (WIN32) + find_library (CBSDK_LIB "cbsdk" PATHS ${PROJECT_SOURCE_DIR}/lib64) + if (NOT CBSDK_LIB) + message (FATAL_ERROR + "cbsdk library missing from ${PROJECT_SOURCE_DIR}/lib64" + ) + endif () +else (WIN32) + set(CBSDK_LIB "") +endif (WIN32) if (WIN32) find_library (CSTIM_LIB "CereStimDLL" PATHS ${PROJECT_SOURCE_DIR}/dll) @@ -375,7 +379,7 @@ else (WIN32) find_package(PkgConfig REQUIRED) pkg_search_module(FFTW REQUIRED fftw3 IMPORTED_TARGET) include_directories(${FFTW_INCLUDE_DIRS}) - set(FFTW_LIB ${FFTW_LIBRARIES}) + set(FFTW_LIB "-L${FFTW_LIBRARY_DIRS} -l${FFTW_LIBRARIES}") endif (WIN32) From b38d08cbf82f50b252a922df53432f120a774afa Mon Sep 17 00:00:00 2001 From: James Bruska Date: Tue, 22 Oct 2024 11:52:04 -0400 Subject: [PATCH 2/2] Add TASK_STATUS and task_time --- docs/NetworkProtocol.rst | 111 +++++++++++++++++++++------------------ src/TaskNetWorker.cpp | 9 ++++ 2 files changed, 68 insertions(+), 52 deletions(-) diff --git a/docs/NetworkProtocol.rst b/docs/NetworkProtocol.rst index 992e7d53..e6c53a32 100644 --- a/docs/NetworkProtocol.rst +++ b/docs/NetworkProtocol.rst @@ -36,11 +36,13 @@ Key The key information for understanding the protocol below. -* Each message sent from the task side has a numerically increasing id number (unsigned 64-bit) +* Each message sent from the task side has a numerically increasing id number (unsigned 64-bit). -* Host (Elemem) responses should carry the id of the message they are responding to +* Host (Elemem) responses should carry the id of the message they are responding to. -* The StatusPanel flag indicates that it changes the status panel +* The StatusPanel flag indicates that it changes the status panel. + +* Anything in angle brackets <> is a placeholder for a value. The word inside of them names the type of the value. ============= Required Messages @@ -49,23 +51,23 @@ Required Messages These messages are required for Elemem to function * CONNECTED: - * Message: {“type”: “CONNECTED”, “id”: 42, “time”: } - * Response: {“type”: “CONNECTED_OK”, “id”: 42, “time”: } + * Message: {"type": "CONNECTED", "id": 42, "time": } + * Response: {"type": "CONNECTED_OK", "id": 42, "time": } * CONFIGURE: - * Message: {“type”: “CONFIGURE”, “data”: {“stim_mode”: “open”, “experiment”: “RepFR2”, “subject”: “R1999J”}, “id”: 42, “time”: } - * Response Ok: {“type”: “CONFIGURE_OK”, “id”: 42, “time”: } - * Response Error: {“type”: “CONFIGURE_ERROR”, “data”: {“error”: “What went wrong”}, “id”: 42, “time”: } - * Note: Experiments using STIMSELECT tags should include optional data entry “tags”: [, , ...] + * Message: {"type": "CONFIGURE", "data": {"stim_mode": "open", "experiment": "RepFR2", "subject": "R1999J"}, "id": 42, "time": } + * Response Ok: {"type": "CONFIGURE_OK", "id": 42, "time": } + * Response Error: {"type": "CONFIGURE_ERROR", "data": {"error": "What went wrong"}, "id": 42, "time": } + * Note: Experiments using STIMSELECT tags should include optional data entry "tags": [, , ...] * READY: - * Message: {“type”: “READY”, “data”: {}, “id”: 42, “time”: } - * Response: {“type”: “START”, “data”: {}, “id”: 42, “time”: } + * Message: {"type": "READY", "data": {}, "id": 42, "time": } + * Response: {"type": "START", "data": {}, "id": 42, "time": } * HEARTBEAT: - * Message: {“type”: “HEARTBEAT”, “data”: {“count”: 27}, “id”: 42, “time”: } - * Response: {“type”: “HEARTBEAT_OK”, “data”: {“count”: 27}, “id”: 42, “time”: } - * Note: After CONFIGURE_OK received, 20 HEARTBEATs sent at 50ms intervals. Task side logs calculated average and max latency, and raises notification window if max greater than 20ms. “data”: “count” increases with each heartbeat. Sent once per second. 8 missed responses stops experiment. + * Message: {"type": "HEARTBEAT", "data": {"count": 27}, "id": 42, "time": } + * Response: {"type": "HEARTBEAT_OK", "data": {"count": 27}, "id": 42, "time": } + * Note: After CONFIGURE_OK received, 20 HEARTBEATs sent at 50ms intervals. Task side logs calculated average and max latency, and raises notification window if max greater than 20ms. "data": "count" increases with each heartbeat. Sent once per second. 8 missed responses stops experiment. ============= Handled Messages @@ -74,112 +76,117 @@ Handled Messages These are messages that Elemem does something as a result of receiving them. * EXIT: - * Message: {“type”: “EXIT”, “data”: {}, “id”: 42, “time”: } + * Message: {"type": "EXIT", "data": {}, "id": 42, "time": } * Response: None * Purpose: Used to end the session +* SESSION: + * Message: {"type": "SESSION", "data": {"session": [int]}, "id": 42, "time": } + * Response: None + * StatusPanel + * TRIAL: - * Message: {“type”: “TRIAL”, “data”: {“trial”: [int], “stim”:[bool]}, “id”: 42, “time”: } + * Message: {"type": "TRIAL", "data": {"trial": [int], "stim":[bool]}, "id": 42, "time": } * Response: None * Purpose: Indicate which trial number you're on * TRIALEND: - * Message: {“type”: “TRIALEND”, “data”: {}, “id”: 42, “time”: } + * Message: {"type": "TRIALEND", "data": {}, "id": 42, "time": } * Reponse: None * Purpose: Indicates the end of a trial * STIMSELECT: - * Message: {“type”: “STIMSELECT”, “data”: {“tag”: }, “id”: 42, “time”: } + * Message: {"type": "STIMSELECT", "data": {"tag": }, "id": 42, "time": } * Response: None * Purpose: Selects the pre-approved stim configuration matching the tag for subsequent stim events. * STIM: - * Message: {“type”: “STIM”, “data”: {}, “id”: 42, “time”: } + * Message: {"type": "STIM", "data": {}, "id": 42, "time": } * Response: None - * Purpose: This triggers one open-loop stim event. Synchronized stimulation during word presentation can instead be triggered by the WORD event with “data”:{“stim”:true}. + * Purpose: This triggers one open-loop stim event. Synchronized stimulation during word presentation can instead be triggered by the WORD event with "data":{"stim":true}. * CLSTIM: - * Message: {“type”: “CLSTIM”, “data”: {“classifyms”: 1366}, “id”: 42, “time”: } + * Message: {"type": "CLSTIM", "data": {"classifyms": 1366}, "id": 42, "time": } * Response: None * Purpose: This initiates a closed-loop classification epoch for the duration in milliseconds specified by classifyms. Stimulation is initiated following this duration as soon as processing is completed if the classification result is below the threshold, typically 0.5. * CLSHAM: - * Message: {“type”: “CLSHAM”, “data”: {“classifyms”: 1366}, “id”: 42, “time”: } + * Message: {"type": "CLSHAM", "data": {"classifyms": 1366}, "id": 42, "time": } * Response: None * Purpose: This initiates a closed-loop classification epoch for the duration in milliseconds specified by classifyms. This is identical to CLSTIM except that no stimulation is performed, and instead an event is simply logged reporting whether or not stim would have been performed. * CLNORMALIZE: - * Message: {“type”: “CLNORMALIZE”, “data”: {“ classifyms”: 1366}, “id”: 42, “time”: } + * Message: {"type": "CLNORMALIZE", "data": {" classifyms": 1366}, "id": 42, "time": } * Response: None * Purpose: This initiates a closed-loop normalization update epoch for the duration in milliseconds specified by classifyms. * WORD: - * Message: {“type”: “WORD”, “data”: {“word”: , “serialpos”: [int], “stim”:[bool]}, “id”: 42, “time”: } + * Message: {"type": "WORD", "data": {"word": , "serialpos": [int], "stim":[bool]}, "id": 42, "time": } * Response: None + * Purpose: This can initiate a stimulation event if the "stim" field is set to true + * Note: The "word" and "serialpos" fields are optional, but should be set if available * StatusPanel -* SESSION: - * Message: {“type”: “SESSION”, “data”: {“session”: [int]}, “id”: 42, “time”: } - * Response: None - * StatusPanel +* TASK_STATUS: + * Message: {"type": "WORD", "data": {"status": }, "id": 42, "time": } -* REST: - * Message: {“type”: “REST”, “data”: {}, “id”: 42, “time”: } +* [DEPRECATED] REST: + * Message: {"type": "REST", "data": {}, "id": 42, "time": } * Response: None * StatusPanel -* ORIENT (Orientation Cross): - * Message: {“type”: “ORIENT”, “data”: {}, “id”: 42, “time”: } +* [DEPRECATED] ORIENT (Orientation Cross): + * Message: {"type": "ORIENT", "data": {}, "id": 42, "time": } * Response: None * StatusPanel -* COUNTDOWN: - * Message: {“type”: “COUNTDOWN”, “data”: {}, “id”: 42, “time”: } +* [DEPRECATED] COUNTDOWN: + * Message: {"type": "COUNTDOWN", "data": {}, "id": 42, "time": } * Response: None * StatusPanel -* DISTRACT: - * Message: {“type”: “DISTRACT”, “data”: {}, “id”: 42, “time”: } +* [DEPRECATED] DISTRACT: + * Message: {"type": "DISTRACT", "data": {}, "id": 42, "time": } * Response: None * StatusPanel -* RECALL: - * Message: {“type”: “RECALL”, “data”: {“duration”: }, “id”: 42, “time”: } +* [DEPRECATED] RECALL: + * Message: {"type": "RECALL", "data": {"duration": }, "id": 42, "time": } * Response: None * StatusPanel -* INSTRUCT: - * Message: {“type”: “INSTRUCT”, “data”: {}, “id”: 42, “time”: } +* [DEPRECATED] INSTRUCT: + * Message: {"type": "INSTRUCT", "data": {}, "id": 42, "time": } * Response: None * StatusPanel -* MATH: - * Message: {“type”: “MATH”, “data”: {“problem”: , “response”: , “response_time_ms”: [int], “correct”: [bool]}, “id”: 42, “time”: } +* [DEPRECATED] MATH: + * Message: {"type": "MATH", "data": {"problem": , "response": , "response_time_ms": [int], "correct": [bool]}, "id": 42, "time": } * Response: None * StatusPanel -* SYNC: - * Message: {“type”: “SYNC”, “data”: {}, “id”: 42, “time”: } +* [DEPRECATED] SYNC: + * Message: {"type": "SYNC", "data": {}, "id": 42, "time": } * Response: None * StatusPanel * [NOT IMPLEMENTED] WAITING: - * Message: {“type”: “WAITING”, “data”: {}, “id”: 42, “time”: } + * Message: {"type": "WAITING", "data": {}, "id": 42, "time": } * Response: None * Note: Used when waiting on user input * StatusPanel * [NOT IMPLEMENTED] ISI (Inter-Stimulus Interval): - * Message: {“type”: “ISI”, “data”: {“duration”: }, “id”: 42, “time”: } + * Message: {"type": "ISI", "data": {"duration": }, "id": 42, "time": } * Response: None * StatusPanel * [NOT IMPLEMENTED] VOCALIZATION: - * Message: {“type”: “VOCALIZATION”, “data”: {}, “id”: 42, “time”: } + * Message: {"type": "VOCALIZATION", "data": {}, "id": 42, "time": } * Response: None * StatusPanel * [NOT IMPLEMENTED] RECALL: - * Message: {“type”: “RECALL”, “data”: {}, “id”: 42, “time”: } + * Message: {"type": "RECALL", "data": {}, "id": 42, "time": } * Response: None * StatusPanel @@ -196,14 +203,14 @@ Logged Events These are the events that are logged. * ELEMEM: - * Message: {“type”: “ELEMEM”, “data”: {“version”: }, “id”: 0, “time”: } + * Message: {"type": "ELEMEM", "data": {"version": }, "id": 0, "id": 42, "time": } * Note: version is the date time string corresponding to the build time, and matches the version displayed under Help/About inside of Elemem. * STIMMING: - * Message: {“type”: “STIMMING”, “data”: {“electrode_pos”: [uint], “electrode_neg”: [uint], “amplitude”: , “frequency”: , “duration”: }, “time”: } + * Message: {"type": "STIMMING", "data": {"electrode_pos": [uint], "electrode_neg": [uint], "amplitude": , "frequency": , "duration": }, "id": 42, "time": } * Note: electrode_pos and electrode_neg are integer channel numbers, 0 indexed. Units for the other values are amplitude:uA, frequency:Hz, duration:us. * EEGSTART: - * Message: {“type”: “EEGSTART”, “data”: {“sub_dir”: }, “id”: 0, “time”: } - * Note: sub_dir is the session directory name on Elemem (without full path information), for example, “R1999J_2021-06-14_15-47-29”. The time value from this is for converting the Elemem system time to the EEG file offsets. + * Message: {"type": "EEGSTART", "data": {"sub_dir": }, "id": 0, "id": 42, "time": } + * Note: sub_dir is the session directory name on Elemem (without full path information), for example, "R1999J_2021-06-14_15-47-29". The time value from this is for converting the Elemem system time to the EEG file offsets. diff --git a/src/TaskNetWorker.cpp b/src/TaskNetWorker.cpp index b79051ea..fc4e5c7a 100644 --- a/src/TaskNetWorker.cpp +++ b/src/TaskNetWorker.cpp @@ -44,6 +44,7 @@ namespace CML { std::string type; uint64_t id = uint64_t(-1); + double time = -1; if (!inp.TryGet(type, "type")) { // Not a command. Ignore. return; @@ -51,6 +52,9 @@ namespace CML { if (!inp.TryGet(id, "id")) { // Leave as uint64_t(-1) to disable. } + if (!inp.TryGet(time, "time")) { + inp.Set(time, "task_time"); + } inp.Set(Time::Get()*1e3, "time"); hndl->event_log.Log(inp.Line()); @@ -136,6 +140,11 @@ namespace CML { status_panel->SetEvent(type); hndl->ExperimentExit(); } + else if (type == "TASK_STATUS") { + RC::RStr status; + inp.Get(status, "data", "status"); + status_panel->SetEvent(status); + } else { if (type == RC::OneOf("ORIENT", "COUNTDOWN", "DISTRACT", "RECALL", "REST",