diff --git a/DeviceAdapters/NIDAQ/NIAnalogInputPort.cpp b/DeviceAdapters/NIDAQ/NIAnalogInputPort.cpp new file mode 100644 index 000000000..44fa8038b --- /dev/null +++ b/DeviceAdapters/NIDAQ/NIAnalogInputPort.cpp @@ -0,0 +1,179 @@ +// DESCRIPTION: Drive multiple analog and digital outputs on NI DAQ +// AUTHOR: Mark Tsuchida, 2015, Nico Stuurman 2022 +// COPYRIGHT: 2015-2016, Open Imaging, Inc., 2022 Altos Labs +// LICENSE: This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later +// version. +// +// This library is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the GNU Lesser General Public License for more +// details. +// +// You should have received a copy of the GNU Lesser General +// Public License along with this library; if not, write to the +// Free Software Foundation, Inc., 51 Franklin Street, Fifth +// Floor, Boston, MA 02110-1301 USA + +#include "NIDAQ.h" + +#include "ModuleInterface.h" + +// +// MultiAnalogInPort +// + +NIAnalogInputPort::NIAnalogInputPort(const std::string& port) : + ErrorTranslator(22000, 22999, &NIAnalogInputPort::SetErrorText), + niPort_(port), + initialized_(false), + running_(false), + state_(0.0) +{ + InitializeDefaultErrorMessages(); +} + + +NIAnalogInputPort::~NIAnalogInputPort() +{ + Shutdown(); +} + + +int NIAnalogInputPort::Initialize() +{ + if (initialized_) + return DEVICE_OK; + + CPropertyAction* pAct = new CPropertyAction(this, &NIAnalogInputPort::OnVoltage); + int err = CreateFloatProperty("Voltage", state_, true, pAct); + if (err != DEVICE_OK) + return err; + + pAct = new CPropertyAction(this, &NIAnalogInputPort::OnMeasuring); + err = CreateStringProperty("Measuring", "false", false, pAct); + if (err != DEVICE_OK) + return err; + AddAllowedValue("Measuring", "false"); + AddAllowedValue("Measuring", "true"); + + initialized_ = true; + return DEVICE_OK; +} + + +int NIAnalogInputPort::Shutdown() +{ + if (!initialized_) + return DEVICE_OK; + + running_ = false; + initialized_ = false; + return DEVICE_OK; +} + + +void NIAnalogInputPort::GetName(char* name) const +{ + CDeviceUtils::CopyLimitedString(name, + (g_DeviceNameNIDAQAIPortPrefix + niPort_).c_str()); +} + + +int NIAnalogInputPort::GetSignal(double& volts) +{ + volts = state_; + return DEVICE_OK; +} + + +int NIAnalogInputPort::GetLimits(double& minVolts, double& maxVolts) +{ + float64 ranges[1024] = {0}; + char hub_name[1024]; + GetHub()->GetName(hub_name); + + int err = DAQmxGetDevAIVoltageRngs(hub_name, ranges, 1024); + if (err != DEVICE_OK) + return err; + + maxVolts = *std::max_element(ranges, ranges + 1024); + minVolts = *std::min_element(ranges, ranges + 1024); + + return DEVICE_OK; +} + + +int NIAnalogInputPort::SetRunning(bool open) +{ + if (open && !running_) + { + int err = GetHub()->StartAIMeasuringForPort(this); + if (err != DEVICE_OK) + return err; + } + else if (!open && running_) + { + int err = GetHub()->StopAIMeasuringForPort(this); + if (err != DEVICE_OK) + return err; + } + + running_ = open; + return DEVICE_OK; +} + + +int NIAnalogInputPort::GetRunning(bool& open) +{ + open = running_; + return DEVICE_OK; +} + + +int NIAnalogInputPort::OnVoltage(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(state_); + } + return DEVICE_OK; +} + +int NIAnalogInputPort::OnMeasuring(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(running_? "true": "false"); + } + else if (eAct == MM::AfterSet) + { + std::string running; + pProp->Get(running); + int err = SetRunning(running.compare("true") == 0); + if (err != DEVICE_OK) + return err; + } + return DEVICE_OK; +} + +int NIAnalogInputPort::UpdateState(float value) +{ + state_ = value; + OnPropertiesChanged(); + return DEVICE_OK; +} + + +int NIAnalogInputPort::TranslateHubError(int err) +{ + if (err == DEVICE_OK) + return DEVICE_OK; + char buf[MM::MaxStrLength]; + if (GetHub()->GetErrorText(err, buf)) + return NewErrorCode(buf); + return NewErrorCode("Unknown hub error"); +} \ No newline at end of file diff --git a/DeviceAdapters/NIDAQ/NIDAQ.cpp b/DeviceAdapters/NIDAQ/NIDAQ.cpp index 01f812d2e..68b495344 100644 --- a/DeviceAdapters/NIDAQ/NIDAQ.cpp +++ b/DeviceAdapters/NIDAQ/NIDAQ.cpp @@ -23,15 +23,20 @@ #include "ModuleInterface.h" +#include +#include + #include #include #include #include +#include const char* g_DeviceNameNIDAQHub = "NIDAQHub"; const char* g_DeviceNameNIDAQAOPortPrefix = "NIDAQAO-"; const char* g_DeviceNameNIDAQDOPortPrefix = "NIDAQDO-"; +const char* g_DeviceNameNIDAQAIPortPrefix = "NIDAQAI-"; const char* g_On = "On"; const char* g_Off = "Off"; @@ -49,7 +54,9 @@ const int ERR_VOLTAGE_OUT_OF_RANGE = 2004; const int ERR_NONUNIFORM_CHANNEL_VOLTAGE_RANGES = 2005; const int ERR_VOLTAGE_RANGE_EXCEEDS_DEVICE_LIMITS = 2006; const int ERR_UNKNOWN_PINS_PER_PORT = 2007; -const int ERR_INVALID_REQUEST = 2008; +const int ERR_UNEXPECTED_AMOUNT_OF_MEASUREMENTS = 2008; +const int ERR_FAILED_TO_OPEN_TRACE = 2009; +const int ERR_INVALID_REQUEST = 2010; @@ -81,6 +88,12 @@ MODULE_API MM::Device* CreateDevice(const char* deviceName) return new DigitalOutputPort(std::string(deviceName). substr(strlen(g_DeviceNameNIDAQDOPortPrefix))); } + else if (std::string(deviceName).substr(0, strlen(g_DeviceNameNIDAQAIPortPrefix)) == + g_DeviceNameNIDAQAIPortPrefix) + { + return new NIAnalogInputPort(std::string(deviceName). + substr(strlen(g_DeviceNameNIDAQAIPortPrefix))); + } return 0; } @@ -99,14 +112,22 @@ NIDAQHub::NIDAQHub () : maxSequenceLength_(1024), sequencingEnabled_(false), sequenceRunning_(false), - minVolts_(0.0), - maxVolts_(5.0), + minVoltsOut_(0.0), + maxVoltsOut_(5.0), sampleRateHz_(10000.0), aoTask_(0), doTask_(0), doHub8_(0), doHub16_(0), - doHub32_(0) + doHub32_(0), + mThread_(0), + expectedMaxVoltsIn_(5.0), + expectedMinVoltsIn_(-5.0), + traceFrequency_(100.0), + traceAmount_(100), + tracePath_("C:/Program Files/Micro-Manager-2.0/CoreLogs"), + measuringTrace_(false), + tThread_(0) { // discover devices available on this computer and list them here std::string defaultDeviceName = ""; @@ -167,7 +188,7 @@ int NIDAQHub::Initialize() niSampleClock_ = "/" + niDeviceName_ + "/do/SampleClock"; // Determine the possible voltage range - int err = GetVoltageRangeForDevice(niDeviceName_, minVolts_, maxVolts_); + int err = GetVoltageRangeForDevice(niDeviceName_, minVoltsOut_, maxVoltsOut_); if (err != DEVICE_OK) return err; @@ -215,6 +236,42 @@ int NIDAQHub::Initialize() // do not return an error to allow the user to switch the triggerport to something that works } + pAct = new CPropertyAction(this, &NIDAQHub::OnExpectedMaxVoltsIn); + err = CreateFloatProperty("Maximum expected measured Voltage", 5.0, false, pAct); + if (err != DEVICE_OK) + return err; + + pAct = new CPropertyAction(this, &NIDAQHub::OnExpectedMinVoltsIn); + err = CreateFloatProperty("Minimum expected measured Voltage", -5.0, false, pAct); + if (err != DEVICE_OK) + return err; + + mThread_ = new InputMonitoringThread(this); + + pAct = new CPropertyAction(this, &NIDAQHub::OnTraceFrequency); + err = CreateFloatProperty("Trace sampling frequency", 10.0, false, pAct); + if (err != DEVICE_OK) + return err; + + pAct = new CPropertyAction(this, &NIDAQHub::OnTraceAmount); + err = CreateIntegerProperty("Total samples taken", 100, false, pAct); + if (err != DEVICE_OK) + return err; + + pAct = new CPropertyAction(this, &NIDAQHub::OnTracePath); + err = CreateStringProperty("Trace folder", "C:/Program Files/Micro-Manager-2.0/CoreLogs", false, pAct); + if (err != DEVICE_OK) + return err; + + pAct = new CPropertyAction(this, &NIDAQHub::OnTraceRunning); + err = CreateStringProperty("Trace Running", "Stopped", false, pAct); + if (err != DEVICE_OK) + return err; + AddAllowedValue("Trace Running", "Stopped"); + AddAllowedValue("Trace Running", "Running"); + + tThread_ = new TraceMonitoringThread(this); + initialized_ = true; return DEVICE_OK; } @@ -225,6 +282,10 @@ int NIDAQHub::Shutdown() if (!initialized_) return DEVICE_OK; + mThread_->Stop(); + mThread_->wait(); + delete mThread_; + int err = StopTask(aoTask_); physicalAOChannels_.clear(); @@ -237,6 +298,10 @@ int NIDAQHub::Shutdown() else if (doHub32_ != 0) delete doHub32_; + tThread_->Stop(); + tThread_->wait(); + delete tThread_; + initialized_ = false; return err; } @@ -251,7 +316,7 @@ void NIDAQHub::GetName(char* name) const int NIDAQHub::DetectInstalledDevices() { std::vector aoPorts = - GetAnalogPortsForDevice(niDeviceName_); + GetAnalogOutputPortsForDevice(niDeviceName_); for (std::vector::const_iterator it = aoPorts.begin(), end = aoPorts.end(); it != end; ++it) @@ -277,14 +342,28 @@ int NIDAQHub::DetectInstalledDevices() } } + std::vector aiPorts = + GetAnalogInputPortsForDevice(niDeviceName_); + + for (std::vector::const_iterator it = aiPorts.begin(), end = aiPorts.end(); + it != end; ++it) + { + MM::Device* pDevice = + ::CreateDevice((g_DeviceNameNIDAQAIPortPrefix + *it).c_str()); + if (pDevice) + { + AddInstalledDevice(pDevice); + } + } + return DEVICE_OK; } int NIDAQHub::GetVoltageLimits(double& minVolts, double& maxVolts) { - minVolts = minVolts_; - maxVolts = maxVolts_; + minVolts = minVoltsOut_; + maxVolts = maxVoltsOut_; return DEVICE_OK; } @@ -469,7 +548,7 @@ NIDAQHub::GetAOTriggerTerminalsForDevice(const std::string& device) std::vector -NIDAQHub::GetAnalogPortsForDevice(const std::string& device) +NIDAQHub::GetAnalogOutputPortsForDevice(const std::string& device) { std::vector result; @@ -489,6 +568,28 @@ NIDAQHub::GetAnalogPortsForDevice(const std::string& device) return result; } + +std::vector +NIDAQHub::GetAnalogInputPortsForDevice(const std::string& device) +{ + std::vector result; + + char ports[4096]; + int32 nierr = DAQmxGetDevAIPhysicalChans(device.c_str(), ports, sizeof(ports)); + if (nierr == 0) + { + boost::split(result, ports, boost::is_any_of(", "), + boost::token_compress_on); + } + else + { + LogMessage(GetNIDetailedErrorForMostRecentCall().c_str()); + LogMessage("Cannot get list of analog ports"); + } + + return result; +} + std::vector NIDAQHub::GetDigitalPortsForDevice(const std::string& device) { @@ -618,7 +719,7 @@ int NIDAQHub::StartAOSequencingTask() const std::string chanList = GetPhysicalChannelListForSequencing(physicalAOChannels_); nierr = DAQmxCreateAOVoltageChan(aoTask_, chanList.c_str(), - "AOSeqChan", minVolts_, maxVolts_, DAQmx_Val_Volts, + "AOSeqChan", minVoltsOut_, maxVoltsOut_, DAQmx_Val_Volts, NULL); if (nierr != 0) { @@ -807,7 +908,161 @@ int NIDAQHub::SetDOPortState(std::string port, uInt32 portWidth, long state) } - int NIDAQHub::StopTask(TaskHandle &task) +int NIDAQHub::StartAIMeasuringForPort(NIAnalogInputPort* port) +{ + //check if port has not already been added + size_t n = physicalAIChannels_.size(); + for (size_t i = 0; i < n; ++i) + { + if (physicalAIChannels_[i] == port) + return DEVICE_OK; + } + physicalAIChannels_.push_back(port); + int err; + if (measuringTrace_) + { + tThread_->Stop(); + tThread_->wait(); + delete tThread_; + + tThread_ = new TraceMonitoringThread(this); + err = tThread_->Start(GetPhysicalChannelListForMeasuring(physicalAIChannels_), expectedMinVoltsIn_, + expectedMaxVoltsIn_, (float) traceFrequency_, traceAmount_, (int) physicalAIChannels_.size()); + } + else + { + mThread_->Stop(); + mThread_->wait(); + delete mThread_; + + mThread_ = new InputMonitoringThread(this); + err = mThread_->Start(GetPhysicalChannelListForMeasuring(physicalAIChannels_), expectedMinVoltsIn_, expectedMaxVoltsIn_); + } + + + return err; +} + + +int NIDAQHub::StopAIMeasuringForPort(NIAnalogInputPort* port) +{ + size_t n = physicalAIChannels_.size(); + for (size_t i = 0; i < n; ++i) + { + if (physicalAIChannels_[i] == port) + { + physicalAIChannels_.erase(physicalAIChannels_.begin() + i); + + if (measuringTrace_) + { + tThread_->Stop(); + tThread_->wait(); + delete tThread_; + + tThread_ = new TraceMonitoringThread(this); + int err = DEVICE_OK; + if (n > 1) + err = tThread_->Start(GetPhysicalChannelListForMeasuring(physicalAIChannels_), expectedMinVoltsIn_, + expectedMaxVoltsIn_, (float) traceFrequency_, traceAmount_, (int) physicalAIChannels_.size()); + + return err; + } + else + { + mThread_->Stop(); + mThread_->wait(); + delete mThread_; + + mThread_ = new InputMonitoringThread(this); + + int err = DEVICE_OK; + if (n > 1) + err = mThread_->Start(GetPhysicalChannelListForMeasuring(physicalAIChannels_), expectedMinVoltsIn_, expectedMaxVoltsIn_); + + return err; + } + } + } + return DEVICE_OK; +} + + +int NIDAQHub::UpdateAIValues(float64* values, int32 amount) +{ + if (amount != 1) + return ERR_UNEXPECTED_AMOUNT_OF_MEASUREMENTS; + + size_t n = physicalAIChannels_.size(); + for (size_t i = 0; i < n; ++i) + { + physicalAIChannels_[i]->UpdateState((float) values[i]); + } + + return DEVICE_OK; +} + + +std::string NIDAQHub::GetPhysicalChannelListForMeasuring(std::vector channels) +{ + std::string result; + size_t n = channels.size(); + for (size_t i = 0; i < n; ++i) + { + result += channels[i]->niPort_; + if (i < n - 1) + result += ", "; + } + return result; +} + + +int NIDAQHub::StartTrace() +{ + measuringTrace_ = true; + mThread_->Stop(); + mThread_->wait(); + + + delete tThread_; + tThread_ = new TraceMonitoringThread(this); + int err = tThread_->Start(GetPhysicalChannelListForMeasuring(physicalAIChannels_), expectedMinVoltsIn_, + expectedMaxVoltsIn_, (float) traceFrequency_, traceAmount_, (int) physicalAIChannels_.size()); + + return err; +} + + +int NIDAQHub::StopTrace() +{ + tThread_->Stop(); + tThread_->wait(); + measuringTrace_ = false; + + + delete mThread_; + mThread_ = new InputMonitoringThread(this); + int err = DEVICE_OK; + if (physicalAIChannels_.size() > 0) + err = mThread_->Start(GetPhysicalChannelListForMeasuring(physicalAIChannels_), expectedMinVoltsIn_, expectedMaxVoltsIn_); + + return err; +} + + +int NIDAQHub::FinishTrace() +{ + measuringTrace_ = false; + OnPropertiesChanged(); + + int err = DEVICE_OK; + if (physicalAIChannels_.size() > 0) + err = mThread_->Start(GetPhysicalChannelListForMeasuring(physicalAIChannels_), expectedMinVoltsIn_, expectedMaxVoltsIn_); + + return err; +} + + +int NIDAQHub::StopTask(TaskHandle &task) { if (!task) return DEVICE_OK; @@ -919,6 +1174,118 @@ int NIDAQHub::OnSampleRate(MM::PropertyBase* pProp, MM::ActionType eAct) } +int NIDAQHub::OnExpectedMaxVoltsIn(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(expectedMaxVoltsIn_); + } + else if (eAct == MM::AfterSet) + { + double temp_max = 5.0; + pProp->Get(temp_max); + expectedMaxVoltsIn_ = (float) temp_max; + + mThread_->Stop(); + mThread_->wait(); + delete mThread_; + mThread_ = new InputMonitoringThread(this); + if (physicalAIChannels_.size() > 1) + mThread_->Start(GetPhysicalChannelListForMeasuring(physicalAIChannels_), expectedMinVoltsIn_, expectedMaxVoltsIn_); + + } + return DEVICE_OK; +} + + +int NIDAQHub::OnExpectedMinVoltsIn(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(expectedMinVoltsIn_); + } + else if (eAct == MM::AfterSet) + { + double temp_min = -5.0; + pProp->Get(temp_min); + expectedMinVoltsIn_ = (float) temp_min; + + mThread_->Stop(); + mThread_->wait(); + delete mThread_; + mThread_ = new InputMonitoringThread(this); + if (physicalAIChannels_.size() > 1) + mThread_->Start(GetPhysicalChannelListForMeasuring(physicalAIChannels_), expectedMinVoltsIn_, expectedMaxVoltsIn_); + } + return DEVICE_OK; +} + + +int NIDAQHub::OnTraceFrequency(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(traceFrequency_); + } + else if (eAct == MM::AfterSet) + { + pProp->Get(traceFrequency_); + } + return DEVICE_OK; +} + + +int NIDAQHub::OnTraceAmount(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(traceAmount_); + } + else if (eAct == MM::AfterSet) + { + pProp->Get(traceAmount_); + } + return DEVICE_OK; +} + + +int NIDAQHub::OnTracePath(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(tracePath_.c_str()); + } + else if (eAct == MM::AfterSet) + { + pProp->Get(tracePath_); + } + return DEVICE_OK; +} + + +int NIDAQHub::OnTraceRunning(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(measuringTrace_? "Running" : "Stopped"); + } + else if (eAct == MM::AfterSet) + { + std::string input; + pProp->Get(input); + if (input == "Running") + { + StartTrace(); + } + else if(input == "Stopped") + { + StopTrace(); + } + } + return DEVICE_OK; +} + + // // NIDAQDOHub // @@ -1263,4 +1630,163 @@ int NIDAQDOHub::DaqmxWriteDigital(TaskHandle doTask, int32 samplesPerCha template class NIDAQDOHub; template class NIDAQDOHub; -template class NIDAQDOHub; \ No newline at end of file +template class NIDAQDOHub; + + +InputMonitoringThread::InputMonitoringThread(NIDAQHub* hub) : + stop_(false), + aiTask_(NULL) +{ + hub_ = hub; +} + + +InputMonitoringThread::~InputMonitoringThread() +{ + Stop(); + wait(); +} + + +int InputMonitoringThread::Start(std::string AIChannelList, float minVal, float maxVal) +{ + stop_ = false; + int err = DAQmxCreateTask("AnalogInputReadTask", &aiTask_); + if (err != DEVICE_OK) + return err; + + err = DAQmxCreateAIVoltageChan(aiTask_, AIChannelList.c_str(), "", DAQmx_Val_RSE, minVal, maxVal, DAQmx_Val_Volts, NULL); + if (err != DEVICE_OK) + return err; + + activate(); + return DEVICE_OK; +} + + +int InputMonitoringThread::svc() +{ + while (!stop_) + { + float64 values[128] = { 0 }; + int32 amount; + int err = DAQmxReadAnalogF64(aiTask_, 1, 2.0, DAQmx_Val_GroupByChannel, values, 128, &amount, NULL); + if (err != DEVICE_OK) + return err; + + hub_->UpdateAIValues(values, amount); + CDeviceUtils::SleepMs(100); + } + int err = DAQmxClearTask(aiTask_); + if (err != DEVICE_OK) + return err; + + return DEVICE_OK; +} + + +TraceMonitoringThread::TraceMonitoringThread(NIDAQHub* hub) : + stop_(false), + totalAmount_(0), + numberOfChannels_(0), + CSVheader_("") +{ + hub_ = hub; + path_ = hub_->tracePath_ + "/trace_"; +} + + +TraceMonitoringThread::~TraceMonitoringThread() +{ + Stop(); + wait(); +} + + +int TraceMonitoringThread::Start(std::string AIChannelList, float minVal, float maxVal, float frequency, int numberOfSamples, int numberOfChannels) +{ + stop_ = false; + int err = DAQmxCreateTask("AnalogInputReadTask", &aiTask_); + if (err != DEVICE_OK) + return err; + + CSVheader_ = "Time, " + AIChannelList; + err = DAQmxCreateAIVoltageChan(aiTask_, AIChannelList.c_str(), "", DAQmx_Val_RSE, minVal, maxVal, DAQmx_Val_Volts, NULL); + if (err != DEVICE_OK) + return err; + + timestep_ = 1 / frequency; + err = DAQmxSetSampClkRate(aiTask_, frequency); + if (err != DEVICE_OK) + return err; + + err = DAQmxSetSampQuantSampMode(aiTask_, DAQmx_Val_FiniteSamps); + if (err != DEVICE_OK) + return err; + + totalAmount_ = numberOfSamples; + err = DAQmxSetSampQuantSampPerChan(aiTask_, numberOfSamples); + if (err != DEVICE_OK) + return err; + + err = DAQmxSetSampTimingType(aiTask_, DAQmx_Val_SampClk); + if (err != DEVICE_OK) + return err; + + path_ += boost::posix_time::to_iso_string(boost::posix_time::second_clock::local_time()) + ".csv"; + numberOfChannels_ = numberOfChannels; + + err = DAQmxStartTask(aiTask_); + if (err != DEVICE_OK) + return err; + + activate(); + return DEVICE_OK; +} + + +int TraceMonitoringThread::svc() +{ + std::ofstream trace(path_); + if (!trace.is_open()) + { + hub_->LogMessage("Could not open trace"); + return ERR_FAILED_TO_OPEN_TRACE; + } + + trace << CSVheader_ << std::endl; + float time = 0; + float64 values[1024] = { 0 }; + + while (!stop_ && totalAmount_ > 0) + { + int32 amount = 0; + int err = DAQmxReadAnalogF64(aiTask_, DAQmx_Val_Auto, -1, DAQmx_Val_GroupByScanNumber, values, 1024, &amount, NULL); + if (err != DEVICE_OK) + return err; + + for (int i = 0; i < amount; i++) + { + trace << time << ", "; + for (int j = 0; j < numberOfChannels_; j++) + { + trace << values[i * numberOfChannels_ + j]; + if (j < numberOfChannels_-1) + trace << ", "; + } + trace << std::endl; + time += timestep_; + } + totalAmount_ -= amount; + CDeviceUtils::SleepMs(100); + } + stop_ = true; + trace.close(); + int err = DAQmxClearTask(aiTask_); + if (err != DEVICE_OK) + return err; + + err = hub_->FinishTrace(); + + return err; +} \ No newline at end of file diff --git a/DeviceAdapters/NIDAQ/NIDAQ.h b/DeviceAdapters/NIDAQ/NIDAQ.h index 1e31d96fe..75c5d9a56 100644 --- a/DeviceAdapters/NIDAQ/NIDAQ.h +++ b/DeviceAdapters/NIDAQ/NIDAQ.h @@ -35,6 +35,7 @@ extern const char* g_DeviceNameNIDAQHub; extern const char* g_DeviceNameNIDAQAOPortPrefix; extern const char* g_DeviceNameNIDAQDOPortPrefix; +extern const char* g_DeviceNameNIDAQAIPortPrefix; extern const char* g_On; extern const char* g_Off; extern const char* g_Low; @@ -51,6 +52,8 @@ extern const int ERR_VOLTAGE_OUT_OF_RANGE; extern const int ERR_NONUNIFORM_CHANNEL_VOLTAGE_RANGES; extern const int ERR_VOLTAGE_RANGE_EXCEEDS_DEVICE_LIMITS; extern const int ERR_UNKNOWN_PINS_PER_PORT; +extern const int ERR_UNEXPECTED_AMOUNT_OF_MEASUREMENTS; +extern const int ERR_FAILED_TO_OPEN_TRACE; inline std::string GetNIError(int32 nierr) @@ -150,6 +153,53 @@ class NIDAQDOHub }; +class InputMonitoringThread : public MMDeviceThreadBase +{ +public: + InputMonitoringThread(NIDAQHub* hub); + ~InputMonitoringThread(); + int svc(); + + int Start(std::string AIChannelList, float minVal, float maxVal); + void Stop() { stop_ = true; } + + +private: + NIDAQHub* hub_; + + TaskHandle aiTask_; + + bool stop_; +}; + + +class TraceMonitoringThread : public MMDeviceThreadBase +{ +public: + TraceMonitoringThread(NIDAQHub* hub); + ~TraceMonitoringThread(); + int svc(); + + int Start(std::string AIChannelList, float minVal, float maxVal, float frequency, int numberOfSamples, int numberOfChannels); + void Stop() { stop_ = true; } + + +private: + NIDAQHub* hub_; + + TaskHandle aiTask_; + std::string path_; + + int totalAmount_; + int numberOfChannels_; + std::string CSVheader_; + float timestep_; + bool stop_; +}; + +// Forward declaration needed for NIDAQ hub +class NIAnalogInputPort; + /** * A hub - peripheral device set for driving multiple analog output ports, * possibly with hardware-triggered sequencing using a shared trigger input. @@ -162,6 +212,8 @@ class NIDAQHub : public HubBase, friend NIDAQDOHub; friend NIDAQDOHub; friend NIDAQDOHub; + friend InputMonitoringThread; + friend TraceMonitoringThread; public: NIDAQHub(); virtual ~NIDAQHub(); @@ -196,13 +248,18 @@ class NIDAQHub : public HubBase, int StopTask(TaskHandle& task); + int StartAIMeasuringForPort(NIAnalogInputPort* port); + int StopAIMeasuringForPort(NIAnalogInputPort* port); + + private: int AddAOPortToSequencing(const std::string& port, const std::vector sequence); void RemoveAOPortFromSequencing(const std::string& port); int GetVoltageRangeForDevice(const std::string& device, double& minVolts, double& maxVolts); std::vector GetAOTriggerTerminalsForDevice(const std::string& device); - std::vector GetAnalogPortsForDevice(const std::string& device); + std::vector GetAnalogOutputPortsForDevice(const std::string& device); + std::vector GetAnalogInputPortsForDevice(const std::string& device); std::vector GetDigitalPortsForDevice(const std::string& device); std::string GetPhysicalChannelListForSequencing(std::vector channels) const; template int GetLCMSamplesPerChannel(size_t& seqLen, std::vector>) const; @@ -212,6 +269,13 @@ class NIDAQHub : public HubBase, int StartAOSequencingTask(); + int UpdateAIValues(float64* values, int32 amount); + std::string GetPhysicalChannelListForMeasuring(std::vector channels); + + int StartTrace(); + int StopTrace(); + int FinishTrace(); + // Action handlers int OnDevice(MM::PropertyBase* pProp, MM::ActionType eAct); int OnMaxSequenceLength(MM::PropertyBase* pProp, MM::ActionType eAct); @@ -220,6 +284,13 @@ class NIDAQHub : public HubBase, int OnTriggerInputPort(MM::PropertyBase* pProp, MM::ActionType eAct); int OnSampleRate(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnExpectedMaxVoltsIn(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnExpectedMinVoltsIn(MM::PropertyBase* pProp, MM::ActionType eAct); + + int OnTraceFrequency(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTraceAmount(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTracePath(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTraceRunning(MM::PropertyBase* pProp, MM::ActionType eAct); bool initialized_; size_t maxSequenceLength_; @@ -231,12 +302,13 @@ class NIDAQHub : public HubBase, std::string niChangeDetection_; std::string niSampleClock_; - double minVolts_; // Min possible for device - double maxVolts_; // Max possible for device + double minVoltsOut_; // Min possible for device + double maxVoltsOut_; // Max possible for device double sampleRateHz_; TaskHandle aoTask_; TaskHandle doTask_; + // For memory safety reasons the the Input thread manages aiTask_ NIDAQDOHub * doHub8_; NIDAQDOHub* doHub16_; @@ -247,6 +319,18 @@ class NIDAQHub : public HubBase, std::vector physicalAOChannels_; // Invariant: all unique std::vector> aoChannelSequences_; + float expectedMaxVoltsIn_; + float expectedMinVoltsIn_; + bool measuringTrace_; + + std::vector physicalAIChannels_; + + double traceFrequency_; + long traceAmount_; + std::string tracePath_; + + InputMonitoringThread* mThread_; + TraceMonitoringThread* tThread_; }; @@ -383,3 +467,58 @@ class DigitalOutputPort : public CStateDeviceBase, TaskHandle task_; }; + +class NIAnalogInputPort : public CSignalIOBase, + ErrorTranslator, + boost::noncopyable +{ + friend NIDAQHub; +public: + NIAnalogInputPort(const std::string& port); + virtual ~NIAnalogInputPort(); + + virtual int Initialize(); + virtual int Shutdown(); + + virtual void GetName(char* name) const; + virtual bool Busy() { return false; } + + virtual int SetGateOpen(bool open = true) { return SetRunning(open); } + virtual int GetGateOpen(bool& open) { return GetRunning(open); } + virtual int SetSignal(double) { return DEVICE_UNSUPPORTED_COMMAND; } + virtual int GetSignal(double& volts); + virtual int GetLimits(double& minVolts, double& maxVolts); + + virtual int IsDASequenceable(bool& isSequenceable) const { isSequenceable = false; return DEVICE_OK; } + virtual int GetDASequenceMaxLength(long&) { return DEVICE_UNSUPPORTED_COMMAND; } + virtual int StartDASequence() { return DEVICE_UNSUPPORTED_COMMAND; } + virtual int StopDASequence() { return DEVICE_UNSUPPORTED_COMMAND; } + virtual int ClearDASequence() { return DEVICE_UNSUPPORTED_COMMAND; } + virtual int AddToDASequence(double) { return DEVICE_UNSUPPORTED_COMMAND; } + virtual int SendDASequence() { return DEVICE_UNSUPPORTED_COMMAND; } + + virtual int SetRunning(bool running); + virtual int GetRunning(bool& running); + +private: + + virtual int UpdateState(float value); + // Pre-init property action handlers + + // Post-init property action handlers + int OnVoltage(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnMeasuring(MM::PropertyBase * pProp, MM::ActionType eAct); + + NIDAQHub* GetHub() const + { + return static_cast(GetParentHub()); + } + int TranslateHubError(int err); + + const std::string niPort_; + + bool initialized_; + + bool running_; + float state_; +}; \ No newline at end of file diff --git a/DeviceAdapters/NIDAQ/NIDAQ.vcxproj b/DeviceAdapters/NIDAQ/NIDAQ.vcxproj index 587636144..b664682e0 100644 --- a/DeviceAdapters/NIDAQ/NIDAQ.vcxproj +++ b/DeviceAdapters/NIDAQ/NIDAQ.vcxproj @@ -90,6 +90,7 @@ + diff --git a/DeviceAdapters/NIDAQ/NIDAQ.vcxproj.filters b/DeviceAdapters/NIDAQ/NIDAQ.vcxproj.filters index 1328d4488..575759146 100644 --- a/DeviceAdapters/NIDAQ/NIDAQ.vcxproj.filters +++ b/DeviceAdapters/NIDAQ/NIDAQ.vcxproj.filters @@ -24,6 +24,9 @@ Source Files + + Source Files + diff --git a/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp b/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp index 4f8e74379..02c9fdcf8 100644 --- a/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp +++ b/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp @@ -30,6 +30,7 @@ DigitalOutputPort::DigitalOutputPort(const std::string& port) : sequenceRunning_(false), blanking_(false), blankOnLow_(true), + open_(true), pos_(0), numPos_(0), portWidth_(0), @@ -148,7 +149,7 @@ int DigitalOutputPort::Initialize() CreateProperty(MM::g_Keyword_Closed_Position, "0", MM::Integer, false); GetGateOpen(open_); - if (supportsBlankingAndSequencing_ && nrOfStateSliders_ >= portWidth_) { + if (supportsBlankingAndSequencing_ && (uint32_t) nrOfStateSliders_ >= portWidth_) { nrOfStateSliders_ = portWidth_ - 1; }