From 6368bbb98007355f3a73e02c9fd99a313e63b168 Mon Sep 17 00:00:00 2001 From: Michael Huth Date: Thu, 22 Aug 2024 13:35:56 +0200 Subject: [PATCH] PUB: Add four zeromq publishers for TP data - The four publishers publish the same json, jst with a different period. There is a filter for live, 1s, 5s and 10s publishing interval. - the data is published from the TP analysis thread including additional information available in the thread through the previous commit. - The additional values are also returned by the thread and collected in the async buffer as well then in TPResult and in TPStorage. - The involved waves and their respective getters were adapted with new elements that the additional data can be stored. - As most of the elements store the same information, thus a constant was introduced with a dimension label list that is used as helper for the wave creation in the getter functions. --- Packages/MIES/MIES_Constants.ipf | 11 + .../MIES/MIES_ForeignFunctionInterface.ipf | 3 +- Packages/MIES/MIES_Publish.ipf | 90 ++++++- Packages/MIES/MIES_Structures.ipf | 27 ++ Packages/MIES/MIES_SweepFormula.ipf | 6 +- Packages/MIES/MIES_TestPulse.ipf | 253 +++++++++++------- Packages/MIES/MIES_WaveDataFolderGetters.ipf | 86 +++--- .../UTF_TestPulseAndTPDuringDAQ.ipf | 118 ++++++++ 8 files changed, 439 insertions(+), 155 deletions(-) diff --git a/Packages/MIES/MIES_Constants.ipf b/Packages/MIES/MIES_Constants.ipf index 0f35d397d6..0c8c9c2e6e 100644 --- a/Packages/MIES/MIES_Constants.ipf +++ b/Packages/MIES/MIES_Constants.ipf @@ -1864,6 +1864,10 @@ StrConstant ANALYSIS_FUNCTION_SE = "analysis function:seal evaluation" StrConstant ANALYSIS_FUNCTION_VM = "analysis function:true resting membrane potential" StrConstant DAQ_TP_STATE_CHANGE_FILTER = "data acquisition:state change" StrConstant ANALYSIS_FUNCTION_AR = "analysis function:access resistance smoke" +StrConstant ZMQ_FILTER_TPRESULT_NOW = "testpulse results live" +StrConstant ZMQ_FILTER_TPRESULT_1S = "testpulse results 1s update" +StrConstant ZMQ_FILTER_TPRESULT_5S = "testpulse results 5s update" +StrConstant ZMQ_FILTER_TPRESULT_10S = "testpulse results 10s update" ///@} /// which is sufficient to represent each sample point time with a distinctive number up to rates of 10 MHz. @@ -2307,3 +2311,10 @@ Constant SUTTER_MAX_MAX_TP_PULSES = 10000 Constant INVALID_SWEEP_NUMBER = -1 StrConstant PERCENT_F_MAX_PREC = "%.15f" + +// If this constant with dimLabels is changed the following functions should be verified: +// +// TP_TSAnalysis +// GetTPResultAsyncBuffer +// GetTPResults (reuses same dimlabels partially) +StrConstant TP_ANALYSIS_DATA_LABELS = "BASELINE;STEADYSTATERES;INSTANTRES;ELEVATED_SS;ELEVATED_INST;NOW;HEADSTAGE;MARKER;NUMBER_OF_TP_CHANNELS;TIMESTAMP;TIMESTAMPUTC;CLAMPMODE;CLAMPAMP;BASELINEFRAC;CYCLEID;TPLENGTHPOINTSADC;PULSELENGTHPOINTSADC;PULSESTARTPOINTSADC;SAMPLINGINTERVALADC;TPLENGTHPOINTSDAC;PULSELENGTHPOINTSDAC;PULSESTARTPOINTSDAC;SAMPLINGINTERVALDAC;" diff --git a/Packages/MIES/MIES_ForeignFunctionInterface.ipf b/Packages/MIES/MIES_ForeignFunctionInterface.ipf index 056ed44318..c5dd086104 100644 --- a/Packages/MIES/MIES_ForeignFunctionInterface.ipf +++ b/Packages/MIES/MIES_ForeignFunctionInterface.ipf @@ -66,7 +66,8 @@ Function/WAVE FFI_GetAvailableMessageFilters() PRESSURE_BREAKIN_FILTER, AUTO_TP_FILTER, AMPLIFIER_CLAMP_MODE_FILTER, \ AMPLIFIER_AUTO_BRIDGE_BALANCE, ANALYSIS_FUNCTION_PB, ANALYSIS_FUNCTION_SE, \ ANALYSIS_FUNCTION_VM, DAQ_TP_STATE_CHANGE_FILTER, \ - ANALYSIS_FUNCTION_AR} + ANALYSIS_FUNCTION_AR, ZMQ_FILTER_TPRESULT_NOW, ZMQ_FILTER_TPRESULT_1S, \ + ZMQ_FILTER_TPRESULT_5S, ZMQ_FILTER_TPRESULT_10S} Note/K wv, "Heartbeat is sent every 5 seconds." diff --git a/Packages/MIES/MIES_Publish.ipf b/Packages/MIES/MIES_Publish.ipf index 534719401e..a354369334 100644 --- a/Packages/MIES/MIES_Publish.ipf +++ b/Packages/MIES/MIES_Publish.ipf @@ -24,19 +24,22 @@ static Function PUB_GetJSONTemplate(string device, variable headstage) End /// @brief Publish the given message as given by the JSON and the filter -static Function PUB_Publish(variable jsonID, string messageFilter) +threadsafe Function PUB_Publish(variable jsonID, string messageFilter, [variable releaseJSON]) variable err string payload - payload = JSON_Dump(jsonID) - JSON_Release(jsonID) + releaseJSON = ParamIsDefault(releaseJSON) ? 1 : !!releaseJSON + payload = JSON_Dump(jsonID) + if(releaseJSON) + JSON_Release(jsonID) + endif AssertOnAndClearRTError() try zeromq_pub_send(messageFilter, payload); AbortOnRTE catch err = ClearRTError() - BUG("Could not publish " + messageFilter + " due to: " + num2str(err)) + BUG_TS("Could not publish " + messageFilter + " due to: " + num2str(err)) endtry End @@ -642,3 +645,82 @@ Function PUB_AccessResistanceSmoke(string device, variable sweepNo, variable hea PUB_Publish(jsonID, ANALYSIS_FUNCTION_AR) End + +threadsafe static Function PUB_AddTPResultEntry(variable jsonId, string path, variable value, string unit) + + if(IsEmpty(unit)) + JSON_AddVariable(jsonID, path, value) + else + JSON_AddTreeObject(jsonID, path) + JSON_AddVariable(jsonID, path + "/value", value) + JSON_AddString(jsonID, path + "/unit", unit) + endif +End + +threadsafe Function PUB_TPResult(STRUCT TPZMQData &tpzmq) + + string path + variable jsonId = JSON_New() + string adUnit = GetADChannelUnit(tpzmq.clampMode) + string daUnit = GetDAChannelUnit(tpzmq.clampMode) + + path = "properties" + JSON_AddTreeObject(jsonID, path) + JSON_AddVariable(jsonID, path + "/tp marker", tpzmq.marker) + JSON_AddString(jsonID, path + "/device", tpzmq.device) + JSON_AddVariable(jsonID, path + "/headstage", tpzmq.headstage) + JSON_AddVariable(jsonID, path + "/clamp mode", tpzmq.clampMode) + + PUB_AddTPResultEntry(jsonId, path + "/time of tp acquisition", tpzmq.now, "s") + PUB_AddTPResultEntry(jsonId, path + "/clamp amplitude", tpzmq.clampAmp, daUnit) + PUB_AddTPResultEntry(jsonId, path + "/tp length ADC", tpzmq.tpLengthPointsADC, "points") + PUB_AddTPResultEntry(jsonId, path + "/pulse duration ADC", tpzmq.pulseLengthPointsADC, "points") + PUB_AddTPResultEntry(jsonId, path + "/pulse start point ADC", tpzmq.pulseStartPointsADC, "point") + PUB_AddTPResultEntry(jsonId, path + "/sample interval ADC", tpzmq.samplingIntervalADC, "ms") + PUB_AddTPResultEntry(jsonId, path + "/tp length DAC", tpzmq.tpLengthPointsDAC, "points") + PUB_AddTPResultEntry(jsonId, path + "/pulse duration DAC", tpzmq.pulseLengthPointsDAC, "points") + PUB_AddTPResultEntry(jsonId, path + "/pulse start point DAC", tpzmq.pulseStartPointsDAC, "point") + PUB_AddTPResultEntry(jsonId, path + "/sample interval DAC", tpzmq.samplingIntervalDAC, "ms") + PUB_AddTPResultEntry(jsonId, path + "/baseline fraction", tpzmq.baselineFrac * ONE_TO_PERCENT, "%") + PUB_AddTPResultEntry(jsonId, path + "/timestamp", tpzmq.timeStamp, "s") + PUB_AddTPResultEntry(jsonId, path + "/timestampUTC", tpzmq.timeStampUTC, "s") + PUB_AddTPResultEntry(jsonId, path + "/tp cycle id", tpzmq.cycleId, "") + + path = "results" + JSON_AddTreeObject(jsonID, path) + PUB_AddTPResultEntry(jsonId, path + "/average baseline steady state", tpzmq.avgBaselineSS, adUnit) + PUB_AddTPResultEntry(jsonId, path + "/average tp steady state", tpzmq.avgTPSS, adUnit) + PUB_AddTPResultEntry(jsonId, path + "/instantaneous", tpzmq.instVal, adUnit) + PUB_AddTPResultEntry(jsonId, path + "/steady state resistance", tpzmq.resistanceSS, "MΩ") + PUB_AddTPResultEntry(jsonId, path + "/instantaneous resistance", tpzmq.resistanceInst, "MΩ") + + PUB_Publish(jsonID, ZMQ_FILTER_TPRESULT_NOW, releaseJSON = 0) + if(PUB_CheckPublishingTime(ZMQ_FILTER_TPRESULT_1S, 1)) + PUB_Publish(jsonID, ZMQ_FILTER_TPRESULT_1S, releaseJSON = 0) + endif + if(PUB_CheckPublishingTime(ZMQ_FILTER_TPRESULT_5S, 5)) + PUB_Publish(jsonID, ZMQ_FILTER_TPRESULT_5S, releaseJSON = 0) + endif + if(PUB_CheckPublishingTime(ZMQ_FILTER_TPRESULT_10S, 10)) + PUB_Publish(jsonID, ZMQ_FILTER_TPRESULT_10S, releaseJSON = 0) + endif + JSON_Release(jsonID) +End + +/// @brief Updates the publishing timestamp in the TUFXOP storage and returns 1 if an update is due (0 otherwise) +threadsafe static Function PUB_CheckPublishingTime(string pubFilter, variable period) + + variable lastTime + variable curTime = DateTime + + TUFXOP_AcquireLock/N=(pubFilter) + lastTime = TSDS_ReadVar(pubFilter, defValue = curTime, create = 1) + if(lastTime + period < curTime) + TSDS_Write(pubFilter, var = curTime + period) + TUFXOP_ReleaseLock/N=(pubFilter) + return 1 + endif + TUFXOP_ReleaseLock/N=(pubFilter) + + return 0 +End diff --git a/Packages/MIES/MIES_Structures.ipf b/Packages/MIES/MIES_Structures.ipf index 7b86357123..0f61022364 100644 --- a/Packages/MIES/MIES_Structures.ipf +++ b/Packages/MIES/MIES_Structures.ipf @@ -646,3 +646,30 @@ Structure SF_PlotMetaData string xAxisLabel // from SF_META_XAXISLABEL constant string yAxisLabel // from SF_META_YAXISLABEL constant EndStructure + +/// @brief Helper structure for TP data transfer to zeromq publisher +Structure TPZMQData + variable marker + string device + variable headstage + variable now + variable clampMode + variable clampAmp + variable tpLengthPointsADC + variable pulseLengthPointsADC + variable pulseStartPointsADC + variable samplingIntervalADC + variable tpLengthPointsDAC + variable pulseLengthPointsDAC + variable pulseStartPointsDAC + variable samplingIntervalDAC + variable baselineFrac + variable timeStamp + variable timeStampUTC + variable cycleId + variable avgBaselineSS + variable avgTPSS + variable instVal + variable resistanceSS + variable resistanceInst +EndStructure diff --git a/Packages/MIES/MIES_SweepFormula.ipf b/Packages/MIES/MIES_SweepFormula.ipf index ca07c9b4cf..af5f233194 100644 --- a/Packages/MIES/MIES_SweepFormula.ipf +++ b/Packages/MIES/MIES_SweepFormula.ipf @@ -3027,9 +3027,9 @@ static Function/WAVE SF_OperationTPImpl(string graph, WAVE/WAVE mode, WAVE/Z sel // Assemble TP data WAVE tpInput.data = SF_AverageTPFromSweep(epochMatches, sweepData) - tpInput.tpLengthPoints = DimSize(tpInput.data, ROWS) - tpInput.duration = (str2num(epochTPPulse[0][EPOCH_COL_ENDTIME]) - str2num(epochTPPulse[0][EPOCH_COL_STARTTIME])) * ONE_TO_MILLI / DimDelta(sweepData, ROWS) - tpInput.baselineFrac = TP_CalculateBaselineFraction(tpInput.duration, tpInput.duration + 2 * tpBaseLinePoints) + tpInput.tpLengthPointsADC = DimSize(tpInput.data, ROWS) + tpInput.pulseLengthPointsADC = (str2num(epochTPPulse[0][EPOCH_COL_ENDTIME]) - str2num(epochTPPulse[0][EPOCH_COL_STARTTIME])) * ONE_TO_MILLI / DimDelta(sweepData, ROWS) + tpInput.baselineFrac = TP_CalculateBaselineFraction(tpInput.pulseLengthPointsADC, tpInput.pulseLengthPointsADC + 2 * tpBaseLinePoints) [WAVE settings, settingsIndex] = GetLastSettingChannel(numericalValues, textualValues, sweepNo, CLAMPMODE_ENTRY_KEY, dacChannelNr, XOP_CHANNEL_TYPE_DAC, DATA_ACQUISITION_MODE) SFH_ASSERT(WaveExists(settings), "Failed to retrieve TP Clamp Mode from LBN") diff --git a/Packages/MIES/MIES_TestPulse.ipf b/Packages/MIES/MIES_TestPulse.ipf index f3e1c629ac..56dfab1b0b 100644 --- a/Packages/MIES/MIES_TestPulse.ipf +++ b/Packages/MIES/MIES_TestPulse.ipf @@ -190,32 +190,24 @@ Function TP_ROAnalysis(dfr, err, errmsg) variable err string errmsg - variable i, j, bufSize - variable posMarker, posAsync, tpBufferSize - variable posBaseline, posSSRes, posInstRes - variable posElevSS, posElevInst + variable i, j, bufSize, headstage, marker + variable posMarker, posAsync + string lbl if(err) ASSERT(0, "RTError " + num2str(err) + " in TP_Analysis thread: " + errmsg) endif - WAVE/SDFR=dfr inData = outData - NVAR/SDFR=dfr now = now - NVAR/SDFR=dfr hsIndex = hsIndex - SVAR/SDFR=dfr device = device - NVAR/SDFR=dfr marker = marker - NVAR/SDFR=dfr activeADCs = activeADCs + WAVE/SDFR=dfr inData = outData + SVAR/SDFR=dfr device = device + headstage = inData[%HEADSTAGE] + marker = inData[%MARKER] WAVE asyncBuffer = GetTPResultAsyncBuffer(device) - bufSize = DimSize(asyncBuffer, ROWS) - posMarker = FindDimLabel(asyncBuffer, LAYERS, "MARKER") - posAsync = FindDimLabel(asyncBuffer, COLS, "ASYNCDATA") - posBaseline = FindDimLabel(asyncBuffer, COLS, "BASELINE") - posSSRes = FindDimLabel(asyncBuffer, COLS, "STEADYSTATERES") - posInstRes = FindDimLabel(asyncBuffer, COLS, "INSTANTRES") - posElevSS = FindDimLabel(asyncBuffer, COLS, "ELEVATED_SS") - posElevInst = FindDimLabel(asyncBuffer, COLS, "ELEVATED_INST") + bufSize = DimSize(asyncBuffer, ROWS) + posMarker = FindDimLabel(asyncBuffer, LAYERS, "MARKER") + posAsync = FindDimLabel(asyncBuffer, COLS, "ASYNCDATA") FindValue/RMD=[][posAsync][posMarker, posMarker]/V=(marker)/T=0 asyncBuffer i = V_Value >= 0 ? V_Row : bufSize @@ -227,26 +219,41 @@ Function TP_ROAnalysis(dfr, err, errmsg) asyncBuffer[bufSize][posAsync][posMarker] = marker endif - asyncBuffer[i][posBaseline][hsIndex] = inData[%BASELINE] - asyncBuffer[i][posSSRes][hsIndex] = inData[%STEADYSTATERES] - asyncBuffer[i][posInstRes][hsIndex] = inData[%INSTANTRES] - asyncBuffer[i][posElevSS][hsIndex] = inData[%ELEVATED_SS] - asyncBuffer[i][posElevInst][hsIndex] = inData[%ELEVATED_INST] - - asyncBuffer[i][posAsync][%NOW] = now + WAVE/T dimLabels = ListToTextWave(TP_ANALYSIS_DATA_LABELS, ";") + for(lbl : dimLabels) + asyncBuffer[i][%$lbl][headstage] = inData[%$lbl] + endfor asyncBuffer[i][posAsync][%REC_CHANNELS] += 1 // got one set of results ready - if(asyncBuffer[i][posAsync][%REC_CHANNELS] == activeADCs) + if(asyncBuffer[i][posAsync][%REC_CHANNELS] == inData[%NUMBER_OF_TP_CHANNELS]) WAVE TPResults = GetTPResults(device) WAVE TPSettings = GetTPSettings(device) - MultiThread TPResults[%BaselineSteadyState][] = asyncBuffer[i][posBaseline][q] - MultiThread TPResults[%ResistanceSteadyState][] = asyncBuffer[i][posSSRes][q] - MultiThread TPResults[%ResistanceInst][] = asyncBuffer[i][posInstRes][q] - MultiThread TPResults[%ElevatedSteadyState][] = asyncBuffer[i][posElevSS][q] - MultiThread TPResults[%ElevatedInst][] = asyncBuffer[i][posElevInst][q] + MultiThread TPResults[%BaselineSteadyState][] = asyncBuffer[i][%BASELINE][q] + MultiThread TPResults[%ResistanceSteadyState][] = asyncBuffer[i][%STEADYSTATERES][q] + MultiThread TPResults[%ResistanceInst][] = asyncBuffer[i][%INSTANTRES][q] + MultiThread TPResults[%ElevatedSteadyState][] = asyncBuffer[i][%ELEVATED_SS][q] + MultiThread TPResults[%ElevatedInst][] = asyncBuffer[i][%ELEVATED_INST][q] + MultiThread TPResults[%NOW][] = asyncBuffer[i][%NOW][q] + MultiThread TPResults[%HEADSTAGE][] = asyncBuffer[i][%HEADSTAGE][q] + MultiThread TPResults[%MARKER][] = asyncBuffer[i][%MARKER][q] + MultiThread TPResults[%NUMBER_OF_TP_CHANNELS][] = asyncBuffer[i][%NUMBER_OF_TP_CHANNELS][q] + MultiThread TPResults[%TIMESTAMP][] = asyncBuffer[i][%TIMESTAMP][q] + MultiThread TPResults[%TIMESTAMPUTC][] = asyncBuffer[i][%TIMESTAMPUTC][q] + MultiThread TPResults[%CLAMPMODE][] = asyncBuffer[i][%CLAMPMODE][q] + MultiThread TPResults[%CLAMPAMP][] = asyncBuffer[i][%CLAMPAMP][q] + MultiThread TPResults[%BASELINEFRAC][] = asyncBuffer[i][%BASELINEFRAC][q] + MultiThread TPResults[%CYCLEID][] = asyncBuffer[i][%CYCLEID][q] + MultiThread TPResults[%TPLENGTHPOINTSADC][] = asyncBuffer[i][%TPLENGTHPOINTSADC][q] + MultiThread TPResults[%PULSELENGTHPOINTSADC][] = asyncBuffer[i][%PULSELENGTHPOINTSADC][q] + MultiThread TPResults[%PULSESTARTPOINTSADC][] = asyncBuffer[i][%PULSESTARTPOINTSADC][q] + MultiThread TPResults[%SAMPLINGINTERVALADC][] = asyncBuffer[i][%SAMPLINGINTERVALADC][q] + MultiThread TPResults[%TPLENGTHPOINTSDAC][] = asyncBuffer[i][%TPLENGTHPOINTSDAC][q] + MultiThread TPResults[%PULSELENGTHPOINTSDAC][] = asyncBuffer[i][%PULSELENGTHPOINTSDAC][q] + MultiThread TPResults[%PULSESTARTPOINTSDAC][] = asyncBuffer[i][%PULSESTARTPOINTSDAC][q] + MultiThread TPResults[%SAMPLINGINTERVALDAC][] = asyncBuffer[i][%SAMPLINGINTERVALDAC][q] // Remove finished results from buffer DeletePoints i, 1, asyncBuffer @@ -268,7 +275,7 @@ Function TP_ROAnalysis(dfr, err, errmsg) TP_AutoAmplitudeAndBaseline(device, TPResults, marker) DQ_ApplyAutoBias(device, TPResults) - TP_RecordTP(device, TPResults, now, marker) + TP_RecordTP(device, TPResults, inData[%NOW]) endif End @@ -884,22 +891,33 @@ threadsafe Function/DF TP_TSAnalysis(dfrInp) DFREF dfrInp variable evalRange, refTime, refPoint, tpStartPoint - variable sampleInt + variable jsonId variable avgBaselineSS, avgTPSS, instVal, evalOffsetPointsCorrected, instPoint + STRUCT TPZMQData tpzmq DFREF dfrOut = NewFreeDataFolder() - WAVE data = dfrInp:param0 - NVAR/SDFR=dfrInp clampAmp = param1 - NVAR/SDFR=dfrInp clampMode = param2 - NVAR/SDFR=dfrInp duration = param3 - NVAR/SDFR=dfrInp baselineFrac = param4 - NVAR/SDFR=dfrInp lengthTPInPoints = param5 - NVAR/SDFR=dfrInp now = param6 - NVAR/SDFR=dfrInp hsIndex = param7 - SVAR/SDFR=dfrInp device = param8 - NVAR/SDFR=dfrInp marker = param9 - NVAR/SDFR=dfrInp activeADCs = param10 + // see TP_PrepareAnalysisDF for order + WAVE data = dfrInp:param0 + NVAR/SDFR=dfrInp clampAmp = param1 + NVAR/SDFR=dfrInp clampMode = param2 + NVAR/SDFR=dfrInp pulseLengthPointsADC = param3 + NVAR/SDFR=dfrInp baselineFrac = param4 + NVAR/SDFR=dfrInp tpLengthPointsADC = param5 + NVAR/SDFR=dfrInp now = param6 + NVAR/SDFR=dfrInp headstage = param7 + SVAR/SDFR=dfrInp device = param8 + NVAR/SDFR=dfrInp marker = param9 + NVAR/SDFR=dfrInp activeADCs = param10 + NVAR/SDFR=dfrInp timeStamp = param11 + NVAR/SDFR=dfrInp timeStampUTC = param12 + NVAR/SDFR=dfrInp cycleId = param13 + NVAR/SDFR=dfrInp pulseStartPointsADC = param14 + NVAR/SDFR=dfrInp samplingIntervalADC = param15 + NVAR/SDFR=dfrInp tpLengthPointsDAC = param16 + NVAR/SDFR=dfrInp pulseLengthPointsDAC = param17 + NVAR/SDFR=dfrInp pulseStartPointsDAC = param18 + NVAR/SDFR=dfrInp samplingIntervalDAC = param19 #if defined(TP_ANALYSIS_DEBUGGING) DEBUGPRINT_TS("Marker: ", var = marker) @@ -910,43 +928,32 @@ threadsafe Function/DF TP_TSAnalysis(dfrInp) colors[0, lengthTPInPoints - 1] = 100 #endif - // Rows: - // 0: base line level - // 1: steady state resistance - // 2: instantaneous resistance - // 3: averaged elevated level (steady state) - // 4: averaged elevated level (instantaneous) - Make/N=5/D dfrOut:outData/WAVE=outData - SetDimLabel ROWS, 0, BASELINE, outData - SetDimLabel ROWS, 1, STEADYSTATERES, outData - SetDimLabel ROWS, 2, INSTANTRES, outData - SetDimLabel ROWS, 3, ELEVATED_SS, outData - SetDimLabel ROWS, 4, ELEVATED_INST, outData - - sampleInt = DimDelta(data, ROWS) - tpStartPoint = baseLineFrac * lengthTPInPoints - evalRange = min(5 / sampleInt, min(duration * 0.2, tpStartPoint * 0.2)) * sampleInt + Make/N=(ItemsInList(TP_ANALYSIS_DATA_LABELS))/D dfrOut:outData/WAVE=outData + SetDimensionLabels(outData, TP_ANALYSIS_DATA_LABELS, ROWS) + + tpStartPoint = baseLineFrac * tpLengthPointsADC + evalRange = min(5 / samplingIntervalADC, min(pulseLengthPointsADC * 0.2, tpStartPoint * 0.2)) * samplingIntervalADC // correct TP_EVAL_POINT_OFFSET for the non-standard sampling interval - evalOffsetPointsCorrected = (TP_EVAL_POINT_OFFSET / sampleInt) * HARDWARE_ITC_MIN_SAMPINT + evalOffsetPointsCorrected = (TP_EVAL_POINT_OFFSET / samplingIntervalADC) * HARDWARE_ITC_MIN_SAMPINT - refTime = (tpStartPoint - evalOffsetPointsCorrected) * sampleInt + refTime = (tpStartPoint - evalOffsetPointsCorrected) * samplingIntervalADC AvgBaselineSS = mean(data, refTime - evalRange, refTime) #if defined(TP_ANALYSIS_DEBUGGING) // color BASE - variable refpt = refTime / sampleInt - colors[refpt - evalRange / sampleInt, refpt] = 50 - DEBUGPRINT_TS("SampleInt: ", var = sampleInt) + variable refpt = refTime / samplingIntervalADC + colors[refpt - evalRange / samplingIntervalADC, refpt] = 50 + DEBUGPRINT_TS("SampleInt: ", var = samplingIntervalADC) DEBUGPRINT_TS("tpStartPoint: ", var = tpStartPoint) DEBUGPRINT_TS("evalRange (ms): ", var = evalRange) - DEBUGPRINT_TS("evalRange in points: ", var = evalRange / sampleInt) + DEBUGPRINT_TS("evalRange in points: ", var = evalRange / samplingIntervalADC) DEBUGPRINT_TS("Base range begin (ms): ", var = refTime - evalRange) DEBUGPRINT_TS("Base range eng (ms): ", var = refTime) DEBUGPRINT_TS("average BaseLine: ", var = AvgBaselineSS) #endif - refTime = (lengthTPInPoints - tpStartPoint - evalOffsetPointsCorrected) * sampleInt + refTime = (tpLengthPointsADC - tpStartPoint - evalOffsetPointsCorrected) * samplingIntervalADC avgTPSS = mean(data, refTime - evalRange, refTime) #if defined(TP_ANALYSIS_DEBUGGING) @@ -954,16 +961,16 @@ threadsafe Function/DF TP_TSAnalysis(dfrInp) DEBUGPRINT_TS("steady state range eng (ms): ", var = refTime) DEBUGPRINT_TS("steady state average: ", var = avgTPSS) // color steady state - refpt = lengthTPInPoints - tpStartPoint - evalOffsetPointsCorrected - colors[refpt - evalRange / sampleInt, refpt] = 50 + refpt = lengthTPInPoints - tpStartPoint - evalOffsetPointsCorrected + colors[refpt - evalRange / samplingIntervalADC, refpt] = 50 // color instantaneous - refpt = tpStartPoint + evalOffsetPointsCorrected - colors[refpt, refpt + 0.25 / sampleInt] = 50 + refpt = tpStartPoint + evalOffsetPointsCorrected + colors[refpt, refpt + 0.25 / samplingIntervalADC] = 50 #endif refPoint = tpStartPoint + evalOffsetPointsCorrected // as data is always too small for threaded execution, the values of V_minRowLoc/V_maxRowLoc are reproducible - WaveStats/P/Q/M=1/R=[refPoint, refPoint + 0.25 / sampleInt] data + WaveStats/P/Q/M=1/R=[refPoint, refPoint + 0.25 / samplingIntervalADC] data instPoint = (clampAmp < 0) ? V_minRowLoc : V_maxRowLoc if(instPoint == -1) // all wave data is NaN @@ -979,27 +986,69 @@ threadsafe Function/DF TP_TSAnalysis(dfrInp) #endif if(clampMode == I_CLAMP_MODE) - outData[1] = (avgTPSS - avgBaselineSS) * MILLI_TO_ONE / (clampAmp * PICO_TO_ONE) * ONE_TO_MEGA - outData[2] = (instVal - avgBaselineSS) * MILLI_TO_ONE / (clampAmp * PICO_TO_ONE) * ONE_TO_MEGA + outData[%STEADYSTATERES] = (avgTPSS - avgBaselineSS) * MILLI_TO_ONE / (clampAmp * PICO_TO_ONE) * ONE_TO_MEGA + outData[%INSTANTRES] = (instVal - avgBaselineSS) * MILLI_TO_ONE / (clampAmp * PICO_TO_ONE) * ONE_TO_MEGA else - outData[1] = (clampAmp * MILLI_TO_ONE) / ((avgTPSS - avgBaselineSS) * PICO_TO_ONE) * ONE_TO_MEGA - outData[2] = (clampAmp * MILLI_TO_ONE) / ((instVal - avgBaselineSS) * PICO_TO_ONE) * ONE_TO_MEGA + outData[%STEADYSTATERES] = (clampAmp * MILLI_TO_ONE) / ((avgTPSS - avgBaselineSS) * PICO_TO_ONE) * ONE_TO_MEGA + outData[%INSTANTRES] = (clampAmp * MILLI_TO_ONE) / ((instVal - avgBaselineSS) * PICO_TO_ONE) * ONE_TO_MEGA endif - outData[0] = avgBaselineSS - outData[3] = avgTPSS - outData[4] = instVal + outData[%BASELINE] = avgBaselineSS + outData[%ELEVATED_SS] = avgTPSS + outData[%ELEVATED_INST] = instVal #if defined(TP_ANALYSIS_DEBUGGING) - DEBUGPRINT_TS("instantaneous resistance: ", var = outData[2]) - DEBUGPRINT_TS("steady state resistance: ", var = outData[1]) + DEBUGPRINT_TS("instantaneous resistance: ", var = outData[%INSTANTRES]) + DEBUGPRINT_TS("steady state resistance: ", var = outData[%STEADYSTATERES]) #endif // additional data copy - variable/G dfrOut:now = now - variable/G dfrOut:hsIndex = hsIndex - string/G dfrOut:device = device - variable/G dfrOut:marker = marker - variable/G dfrOut:activeADCs = activeADCs + string/G dfrOut:device = device + outData[%NOW] = now + outData[%HEADSTAGE] = headstage + outData[%MARKER] = marker + outData[%NUMBER_OF_TP_CHANNELS] = activeADCs + outData[%TIMESTAMP] = timestamp + outData[%TIMESTAMPUTC] = timestampUTC + outData[%CLAMPMODE] = clampMode + outData[%CLAMPAMP] = clampAmp + outData[%BASELINEFRAC] = baselineFrac + outData[%CYCLEID] = cycleId + outData[%TPLENGTHPOINTSADC] = tpLengthPointsADC + outData[%PULSELENGTHPOINTSADC] = pulseLengthPointsADC + outData[%PULSESTARTPOINTSADC] = pulseStartPointsADC + outData[%SAMPLINGINTERVALADC] = samplingIntervalADC + outData[%TPLENGTHPOINTSDAC] = tpLengthPointsDAC + outData[%PULSELENGTHPOINTSDAC] = pulseLengthPointsDAC + outData[%PULSESTARTPOINTSDAC] = pulseStartPointsDAC + outData[%SAMPLINGINTERVALDAC] = samplingIntervalDAC + + // Serialize for ZMQ publishing + tpzmq.marker = marker + tpzmq.device = device + tpzmq.headstage = headstage + tpzmq.now = now + tpzmq.clampMode = clampMode + tpzmq.clampAmp = clampAmp + tpzmq.tpLengthPointsADC = tpLengthPointsADC + tpzmq.pulseLengthPointsADC = pulseLengthPointsADC + tpzmq.pulseStartPointsADC = pulseStartPointsADC + tpzmq.samplingIntervalADC = samplingIntervalADC + tpzmq.tpLengthPointsDAC = tpLengthPointsDAC + tpzmq.pulseLengthPointsDAC = pulseLengthPointsDAC + tpzmq.pulseStartPointsDAC = pulseStartPointsDAC + tpzmq.samplingIntervalDAC = samplingIntervalDAC + tpzmq.baselineFrac = baselineFrac + tpzmq.timeStamp = timeStamp + tpzmq.timeStampUTC = timeStampUTC + tpzmq.cycleId = cycleId + + tpzmq.avgBaselineSS = avgBaselineSS + tpzmq.avgTPSS = avgTPSS + tpzmq.instVal = instVal + tpzmq.resistanceSS = outData[%STEADYSTATERES] + tpzmq.resistanceInst = outData[%INSTANTRES] + + PUB_TPResult(tpzmq) return dfrOut End @@ -1031,12 +1080,9 @@ End /// @brief Records values from TPResults into TPStorage at defined intervals. /// /// Used for analysis of TP over time. -static Function TP_RecordTP(device, TPResults, now, tpMarker) - string device - WAVE TPResults - variable now, tpMarker +static Function TP_RecordTP(string device, WAVE TPResults, variable now) - variable delta, i, ret, lastPressureCtrl, timestamp, cycleID + variable delta, i, ret, lastPressureCtrl WAVE TPStorage = GetTPStorage(device) WAVE hsProp = GetHSProperties(device) variable count = GetNumberFromWaveNote(TPStorage, NOTE_INDEX) @@ -1076,14 +1122,9 @@ static Function TP_RecordTP(device, TPResults, now, tpMarker) : TPStorage[count][q][%HoldingCmd_IC] endif - TPStorage[count][][%TimeInSeconds] = now - - // store the current time in a variable first - // so that all columns have the same timestamp - timestamp = DateTime - TPStorage[count][][%TimeStamp] = timestamp - timestamp = DateTimeInUTC() - TPStorage[count][][%TimeStampSinceIgorEpochUTC] = timestamp + TPStorage[count][][%TimeInSeconds] = TPResults[%NOW][q] + TPStorage[count][][%TimeStamp] = TPResults[%TIMESTAMP][q] + TPStorage[count][][%TimeStampSinceIgorEpochUTC] = TPResults[%TIMESTAMPUTC][q] TPStorage[count][][%PeakResistance] = min(TPResults[%ResistanceInst][q], TP_MAX_VALID_RESISTANCE) TPStorage[count][][%SteadyStateResistance] = min(TPResults[%ResistanceSteadyState][q], TP_MAX_VALID_RESISTANCE) @@ -1094,21 +1135,29 @@ static Function TP_RecordTP(device, TPResults, now, tpMarker) TPStorage[count][][%ADC] = hsProp[q][%ADC] TPStorage[count][][%Headstage] = hsProp[q][%Enabled] ? q : NaN TPStorage[count][][%ClampMode] = hsProp[q][%ClampMode] + TPStorage[count][][%CLAMPAMP] = TPResults[%CLAMPAMP][q] TPStorage[count][][%Baseline_VC] = hsProp[q][%ClampMode] == V_CLAMP_MODE ? TPResults[%BaselineSteadyState][q] : NaN TPStorage[count][][%Baseline_IC] = hsProp[q][%ClampMode] == I_CLAMP_MODE ? TPResults[%BaselineSteadyState][q] : NaN TPStorage[count][][%DeltaTimeInSeconds] = count > 0 ? now - TPStorage[0][0][%TimeInSeconds] : 0 - TPStorage[count][][%TPMarker] = tpMarker + TPStorage[count][][%TPMarker] = TPResults[%MARKER][q] - cycleID = ROVAR(GetTestpulseCycleID(device)) - TPStorage[count][][%TPCycleID] = cycleID + TPStorage[count][][%TPCycleID] = TPResults[%CYCLEID][q] TPStorage[count][][%AutoTPAmplitude] = TPResults[%AutoTPAmplitude][q] TPStorage[count][][%AutoTPBaseline] = TPResults[%AutoTPBaseline][q] TPStorage[count][][%AutoTPBaselineRangeExceeded] = TPResults[%AutoTPBaselineRangeExceeded][q] TPStorage[count][][%AutoTPBaselineFitResult] = TPResults[%AutoTPBaselineFitResult][q] TPStorage[count][][%AutoTPDeltaV] = TPResults[%AutoTPDeltaV][q] + TPStorage[count][][%TPLENGTHPOINTSADC] = TPResults[%TPLENGTHPOINTSADC][q] + TPStorage[count][][%PULSELENGTHPOINTSADC] = TPResults[%PULSELENGTHPOINTSADC][q] + TPStorage[count][][%PULSESTARTPOINTSADC] = TPResults[%PULSESTARTPOINTSADC][q] + TPStorage[count][][%SAMPLINGINTERVALADC] = TPResults[%SAMPLINGINTERVALADC][q] + TPStorage[count][][%TPLENGTHPOINTSDAC] = TPResults[%TPLENGTHPOINTSDAC][q] + TPStorage[count][][%PULSELENGTHPOINTSDAC] = TPResults[%PULSELENGTHPOINTSDAC][q] + TPStorage[count][][%PULSESTARTPOINTSDAC] = TPResults[%PULSESTARTPOINTSDAC][q] + TPStorage[count][][%SAMPLINGINTERVALDAC] = TPResults[%SAMPLINGINTERVALDAC][q] WAVE TPSettings = GetTPSettings(device) TPStorage[count][][%AutoTPCycleID] = hsProp[q][%Enabled] ? TPSettings[%autoTPCycleID][q] : NaN diff --git a/Packages/MIES/MIES_WaveDataFolderGetters.ipf b/Packages/MIES/MIES_WaveDataFolderGetters.ipf index a2627a860e..5417290858 100644 --- a/Packages/MIES/MIES_WaveDataFolderGetters.ipf +++ b/Packages/MIES/MIES_WaveDataFolderGetters.ipf @@ -495,22 +495,15 @@ End /// - Layers: /// - 0: marker /// - 1: received channels -/// - 2: now -/// Column 1: baseline level -/// Column 2: steady state res -/// Column 3: instantaneous res -/// Column 4: baseline position -/// Column 5: steady state res position -/// Column 6: instantaneous res position -/// Column 7: average elevated level (steady state) -/// Column 8: average elevated level (instantaneous) +/// Column 1+: defined through TP_ANALYSIS_DATA_LABELS /// /// Layers: -/// - NUM_HEADSTAGES positions with value entries at hsIndex +/// - NUM_HEADSTAGES positions with value entries at headstage Function/WAVE GetTPResultAsyncBuffer(device) string device - variable versionOfNewWave = 1 + variable versionOfNewWave = 2 + variable numCols = ItemsInList(TP_ANALYSIS_DATA_LABELS) + 1 DFREF dfr = GetDeviceTestPulse(device) @@ -519,24 +512,16 @@ Function/WAVE GetTPResultAsyncBuffer(device) if(ExistsWithCorrectLayoutVersion(wv, versionOfNewWave)) return wv elseif(WaveExists(wv)) - Redimension/N=(-1, 9, -1) wv + Redimension/N=(-1, numCols, -1) wv else - Make/N=(0, 9, NUM_HEADSTAGES)/D dfr:TPResultAsyncBuffer/WAVE=wv + Make/N=(0, numCols, NUM_HEADSTAGES)/D dfr:TPResultAsyncBuffer/WAVE=wv endif SetDimLabel COLS, 0, ASYNCDATA, wv - SetDimLabel COLS, 1, BASELINE, wv - SetDimLabel COLS, 2, STEADYSTATERES, wv - SetDimLabel COLS, 3, INSTANTRES, wv - SetDimLabel COLS, 4, BASELINE_POS, wv - SetDimLabel COLS, 5, STEADYSTATERES_POS, wv - SetDimLabel COLS, 6, INSTANTRES_POS, wv - SetDimLabel COLS, 7, ELEVATED_SS, wv - SetDimLabel COLS, 8, ELEVATED_INST, wv + SetDimensionLabels(wv, TP_ANALYSIS_DATA_LABELS, COLS, startPos = 1) SetDimLabel LAYERS, 0, MARKER, wv SetDimLabel LAYERS, 1, REC_CHANNELS, wv - SetDimLabel LAYERS, 2, NOW, wv SetWaveVersion(wv, versionOfNewWave) @@ -3018,38 +3003,34 @@ End /// state is switched (aka on->off or off->on) /// - 29: Auto TP Baseline Fit result: One of @ref TPBaselineFitResults /// - 30: Auto TP Delta V [mV] +/// - 31: Clamp Amplitude [mV] or [pA] depending on Clamp Mode +/// - 32: Testpulse full length in points for AD channel [points] +/// - 33: Testpulse pulse length in points for AD channel [points] +/// - 34: Point index of pulse start for AD channel [point] +/// - 35: Sampling interval for AD channel [ms] +/// - 36: Testpulse full length in points for DA channel [points] +/// - 37: Testpulse pulse length in points for DA channel [points] +/// - 38: Point index of pulse start for DA channel [point] +/// - 39: Sampling interval for DA channel [ms] Function/WAVE GetTPStorage(device) string device DFREF dfr = GetDeviceTestPulse(device) - variable versionOfNewWave = 15 + variable versionOfNewWave = 16 WAVE/Z/SDFR=dfr/D wv = TPStorage if(ExistsWithCorrectLayoutVersion(wv, versionOfNewWave)) return wv elseif(WaveExists(wv)) - Redimension/N=(-1, NUM_HEADSTAGES, 31)/D wv + Redimension/N=(-1, NUM_HEADSTAGES, 39)/D wv - if(WaveVersionIsSmaller(wv, 10)) + if(WaveVersionIsSmaller(wv, 16)) wv[][][17] = NaN - wv[][][20, 21] = NaN - endif - if(WaveVersionIsSmaller(wv, 11)) - wv[][][22] = NaN - endif - // no size change on version 12 - if(WaveVersionIsSmaller(wv, 13)) - wv[][][23] = NaN - endif - if(WaveVersionIsSmaller(wv, 14)) - wv[][][24] = NaN - endif - if(WaveVersionIsSmaller(wv, 15)) - wv[][][25, 30] = NaN + wv[][][20, 39] = NaN endif else - Make/N=(MINIMUM_WAVE_SIZE_LARGE, NUM_HEADSTAGES, 31)/D dfr:TPStorage/WAVE=wv + Make/N=(MINIMUM_WAVE_SIZE_LARGE, NUM_HEADSTAGES, 40)/D dfr:TPStorage/WAVE=wv wv = NaN @@ -3089,6 +3070,15 @@ Function/WAVE GetTPStorage(device) SetDimLabel LAYERS, 28, AutoTPCycleID, wv SetDimLabel LAYERS, 29, AutoTPBaselineFitResult, wv SetDimLabel LAYERS, 30, AutoTPDeltaV, wv + SetDimLabel LAYERS, 31, CLAMPAMP, wv + SetDimLabel LAYERS, 32, TPLENGTHPOINTSADC, wv + SetDimLabel LAYERS, 33, PULSELENGTHPOINTSADC, wv + SetDimLabel LAYERS, 34, PULSESTARTPOINTSADC, wv + SetDimLabel LAYERS, 35, SAMPLINGINTERVALADC, wv + SetDimLabel LAYERS, 36, TPLENGTHPOINTSDAC, wv + SetDimLabel LAYERS, 37, PULSELENGTHPOINTSDAC, wv + SetDimLabel LAYERS, 38, PULSESTARTPOINTSDAC, wv + SetDimLabel LAYERS, 39, SAMPLINGINTERVALDAC, wv SetNumberInWaveNote(wv, AUTOBIAS_LAST_INVOCATION_KEY, 0) SetNumberInWaveNote(wv, DIMENSION_SCALING_LAST_INVOC, 0) @@ -3272,11 +3262,17 @@ End /// - 7: Auto TP Baseline range exceeded: True/False /// - 8: Auto TP Baseline fit result: One of @ref TPBaselineFitResults /// - 9: Auto TP Delta V: [mV] -/// +/// - 10+: partial dim labels from TP_ANALYSIS_DATA_LABELS, originally filled in TP_TSAnalysis /// Columns: /// - NUM_HEADSTAGES Function/WAVE GetTPResults(string device) - variable version = 3 + variable version = 4 + + string labels = "ResistanceInst;BaselineSteadyState;ResistanceSteadyState;ElevatedSteadyState;ElevatedInst;" + \ + "AutoTPAmplitude;AutoTPBaseline;AutoTPBaselineRangeExceeded;AutoTPBaselineFitResult;AutoTPDeltaV;" + \ + "NOW;HEADSTAGE;MARKER;NUMBER_OF_TP_CHANNELS;TIMESTAMP;TIMESTAMPUTC;CLAMPMODE;CLAMPAMP;BASELINEFRAC;" + \ + "CYCLEID;TPLENGTHPOINTSADC;PULSELENGTHPOINTSADC;PULSESTARTPOINTSADC;SAMPLINGINTERVALADC;TPLENGTHPOINTSDAC;" + \ + "PULSELENGTHPOINTSDAC;PULSESTARTPOINTSDAC;SAMPLINGINTERVALDAC;" DFREF dfr = GetDeviceTestPulse(device) WAVE/D/Z/SDFR=dfr wv = results @@ -3284,10 +3280,10 @@ Function/WAVE GetTPResults(string device) if(ExistsWithCorrectLayoutVersion(wv, version)) return wv elseif(WaveExists(wv)) - Redimension/D/N=(10, NUM_HEADSTAGES) wv + Redimension/D/N=(ItemsInList(labels), NUM_HEADSTAGES) wv wv = NaN else - Make/D/N=(10, NUM_HEADSTAGES) dfr:results/WAVE=wv + Make/D/N=(ItemsInList(labels), NUM_HEADSTAGES) dfr:results/WAVE=wv wv = NaN // initialize with the old 1D waves @@ -3303,7 +3299,7 @@ Function/WAVE GetTPResults(string device) KillOrMoveToTrash(wv = SSResistance) endif - SetDimensionLabels(wv, "ResistanceInst;BaselineSteadyState;ResistanceSteadyState;ElevatedSteadyState;ElevatedInst;AutoTPAmplitude;AutoTPBaseline;AutoTPBaselineRangeExceeded;AutoTPBaselineFitResult;AutoTPDeltaV", ROWS) + SetDimensionLabels(wv, labels, ROWS) SetWaveVersion(wv, version) diff --git a/Packages/tests/HardwareBasic/UTF_TestPulseAndTPDuringDAQ.ipf b/Packages/tests/HardwareBasic/UTF_TestPulseAndTPDuringDAQ.ipf index c050328907..7848691417 100644 --- a/Packages/tests/HardwareBasic/UTF_TestPulseAndTPDuringDAQ.ipf +++ b/Packages/tests/HardwareBasic/UTF_TestPulseAndTPDuringDAQ.ipf @@ -1318,3 +1318,121 @@ static Function TPZerosDAC_REENTRY([STRUCT IUTF_MDATA &md]) CHECK_LE_VAR(HW_ReadADC(hardwareType, deviceID, ADC), 0.01) End + +/// UTF_TD_GENERATOR DeviceNameGeneratorMD1 +static Function TestTPPublishing([str]) + string str + + STRUCT DAQSettings s + InitDAQSettingsFromString(s, "MD1_RA0_I0_L0_BKG1_STP1_TP1" + \ + "__HS0_DA0_AD0_CM:IC:_ST:TestPulse:") + + AcquireData_NG(s, str) + + CtrlNamedBackGround StopTPAfterFiveSeconds, start=(ticks + TP_DURATION_S * 60), period=1, proc=StopTPAfterFiveSeconds_IGNORE + PrepareForPublishTest() +End + +static Function TestTPPublishing_REENTRY([str]) + string str + variable sweepNo, jsonId, var + string msg, filter, stv + + CHECK_EQUAL_VAR(GetSetVariable(str, "SetVar_Sweep"), 0) + + sweepNo = AFH_GetLastSweepAcquired(str) + CHECK_EQUAL_VAR(sweepNo, NaN) + + WaitAndCheckStoredTPs_IGNORE(str, 1) + + Make/FREE/T filters = {ZMQ_FILTER_TPRESULT_NOW, ZMQ_FILTER_TPRESULT_1S, ZMQ_FILTER_TPRESULT_5S, ZMQ_FILTER_TPRESULT_10S} + for(filter : filters) + msg = FetchPublishedMessage(filter) + jsonId = JSON_Parse(msg) + + var = JSON_GetVariable(jsonID, "/properties/tp marker") + CHECK_EQUAL_VAR(IsInteger(var), 1) + stv = JSON_GetString(jsonID, "/properties/device") + CHECK_EQUAL_STR(stv, str) + var = JSON_GetVariable(jsonID, "/properties/headstage") + CHECK_EQUAL_VAR(var, 0) + var = JSON_GetVariable(jsonID, "/properties/clamp mode") + CHECK_EQUAL_VAR(var, I_CLAMP_MODE) + + var = JSON_GetVariable(jsonID, "/properties/time of tp acquisition/value") + CHECK_GT_VAR(var, 0) + stv = JSON_GetString(jsonID, "/properties/time of tp acquisition/unit") + CHECK_EQUAL_STR(stv, "s") + var = JSON_GetVariable(jsonID, "/properties/clamp amplitude/value") + CHECK_LT_VAR(var, 0) + stv = JSON_GetString(jsonID, "/properties/clamp amplitude/unit") + CHECK_EQUAL_STR(stv, "pA") + var = JSON_GetVariable(jsonID, "/properties/tp length ADC/value") + CHECK_EQUAL_VAR(IsInteger(var), 1) + stv = JSON_GetString(jsonID, "/properties/tp length ADC/unit") + CHECK_EQUAL_STR(stv, "points") + var = JSON_GetVariable(jsonID, "/properties/pulse duration ADC/value") + CHECK_EQUAL_VAR(IsInteger(var), 1) + stv = JSON_GetString(jsonID, "/properties/pulse duration ADC/unit") + CHECK_EQUAL_STR(stv, "points") + var = JSON_GetVariable(jsonID, "/properties/pulse start point ADC/value") + CHECK_EQUAL_VAR(IsInteger(var), 1) + stv = JSON_GetString(jsonID, "/properties/pulse start point ADC/unit") + CHECK_EQUAL_STR(stv, "point") + var = JSON_GetVariable(jsonID, "/properties/sample interval ADC/value") + CHECK_GT_VAR(var, 0) + stv = JSON_GetString(jsonID, "/properties/sample interval ADC/unit") + CHECK_EQUAL_STR(stv, "ms") + var = JSON_GetVariable(jsonID, "/properties/tp length DAC/value") + CHECK_EQUAL_VAR(IsInteger(var), 1) + stv = JSON_GetString(jsonID, "/properties/tp length DAC/unit") + CHECK_EQUAL_STR(stv, "points") + var = JSON_GetVariable(jsonID, "/properties/pulse duration DAC/value") + CHECK_EQUAL_VAR(IsInteger(var), 1) + stv = JSON_GetString(jsonID, "/properties/pulse duration DAC/unit") + CHECK_EQUAL_STR(stv, "points") + var = JSON_GetVariable(jsonID, "/properties/pulse start point DAC/value") + CHECK_EQUAL_VAR(IsInteger(var), 1) + stv = JSON_GetString(jsonID, "/properties/pulse start point DAC/unit") + CHECK_EQUAL_STR(stv, "point") + var = JSON_GetVariable(jsonID, "/properties/sample interval DAC/value") + CHECK_GT_VAR(var, 0) + stv = JSON_GetString(jsonID, "/properties/sample interval DAC/unit") + CHECK_EQUAL_STR(stv, "ms") + var = JSON_GetVariable(jsonID, "/properties/baseline fraction/value") + CHECK_EQUAL_VAR(var, 35) + stv = JSON_GetString(jsonID, "/properties/baseline fraction/unit") + CHECK_EQUAL_STR(stv, "%") + var = JSON_GetVariable(jsonID, "/properties/timestamp/value") + CHECK_GT_VAR(var, 0) + stv = JSON_GetString(jsonID, "/properties/timestamp/unit") + CHECK_EQUAL_STR(stv, "s") + var = JSON_GetVariable(jsonID, "/properties/timestampUTC/value") + CHECK_GT_VAR(var, 0) + stv = JSON_GetString(jsonID, "/properties/timestampUTC/unit") + CHECK_EQUAL_STR(stv, "s") + + var = JSON_GetVariable(jsonID, "/properties/tp cycle id") + + var = JSON_GetVariable(jsonID, "/results/average baseline steady state/value") + CHECK_NEQ_VAR(var, NaN) + stv = JSON_GetString(jsonID, "/results/average baseline steady state/unit") + CHECK_EQUAL_STR(stv, "mV") + var = JSON_GetVariable(jsonID, "/results/average tp steady state/value") + CHECK_NEQ_VAR(var, NaN) + stv = JSON_GetString(jsonID, "/results/average tp steady state/unit") + CHECK_EQUAL_STR(stv, "mV") + var = JSON_GetVariable(jsonID, "/results/instantaneous/value") + CHECK_NEQ_VAR(var, NaN) + stv = JSON_GetString(jsonID, "/results/instantaneous/unit") + CHECK_EQUAL_STR(stv, "mV") + var = JSON_GetVariable(jsonID, "/results/steady state resistance/value") + CHECK_NEQ_VAR(var, NaN) + stv = JSON_GetString(jsonID, "/results/steady state resistance/unit") + CHECK_EQUAL_STR(stv, "MΩ") + var = JSON_GetVariable(jsonID, "/results/instantaneous resistance/value") + CHECK_NEQ_VAR(var, NaN) + stv = JSON_GetString(jsonID, "/results/instantaneous resistance/unit") + CHECK_EQUAL_STR(stv, "MΩ") + endfor +End