From 31fc27473dcf3533a6d31db40ce737f2c2f128e7 Mon Sep 17 00:00:00 2001 From: stonelin Date: Tue, 16 Jan 2024 13:48:08 +0800 Subject: [PATCH] Load xdaq devices plugin --- .clang-format | 3 + .gitignore | 2 + CMakeLists.txt | 22 +- Engine/API/Hardware/controller_info.cpp | 53 + Engine/API/Hardware/controller_info.h | 22 + Engine/API/Hardware/rhxcontroller.cpp | 222 +-- Engine/API/Hardware/rhxcontroller.h | 65 +- Engine/CMakeLists.txt | 4 +- .../Processing/{semaphore.h => Semaphore.h} | 0 Engine/Processing/datastreamfifo.h | 2 +- Engine/Processing/waveformfifo.h | 2 +- GUI/Dialogs/boardselectdialog.cpp | 1191 +++++++---------- GUI/Dialogs/boardselectdialog.h | 126 +- conanfile.txt | 10 + main.cpp | 321 ++++- 15 files changed, 1006 insertions(+), 1039 deletions(-) create mode 100644 Engine/API/Hardware/controller_info.cpp create mode 100644 Engine/API/Hardware/controller_info.h rename Engine/Processing/{semaphore.h => Semaphore.h} (100%) create mode 100644 conanfile.txt diff --git a/.clang-format b/.clang-format index 2ec7456..7b7b371 100644 --- a/.clang-format +++ b/.clang-format @@ -14,3 +14,6 @@ UseTab: Never IndentWidth: 4 BreakBeforeBraces: Linux AccessModifierOffset: -4 +BinPackArguments: false +AlignAfterOpenBracket: BlockIndent +AllowAllArgumentsOnNextLine: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4a5d6d3..83a3db9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ # Build /build /.venv +/.cache +/CMakeUserPresets.json \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b06488..95d5c29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ set(CMAKE_AUTORCC ON) find_package(Qt5 REQUIRED COMPONENTS Core Gui Multimedia Network Widgets Xml) find_package(OpenCL REQUIRED) find_package(okFrontPanel REQUIRED) +find_package(xdaq REQUIRED) set(ICON_NAME "xdaq-icon.icns") set(ICON_PATH ${PROJECT_SOURCE_DIR}/images/${ICON_NAME}) @@ -54,9 +55,16 @@ target_include_directories(IntanRHX PRIVATE message(STATUS "Qt5 : ${Qt5_DIR}") -target_compile_features(IntanRHX PUBLIC cxx_std_14) +target_compile_features(IntanRHX PUBLIC cxx_std_17) target_compile_options(IntanRHX PRIVATE $<$:-Wno-deprecated>) target_compile_definitions(IntanRHX PUBLIC $<$:DEBUG>) +find_package(nlohmann_json REQUIRED) +find_package(fmt REQUIRED) +option(Sanitize "Enable sanitizers" OFF) +# if(Sanitize) +# target_compile_options(IntanRHX PRIVATE $<$:-fsanitize=address,undefined>) +# target_link_options(IntanRHX PRIVATE $<$:-fsanitize=address,undefined>) +# endif() target_link_libraries( IntanRHX @@ -68,17 +76,13 @@ target_link_libraries( Qt5::Widgets Qt5::Xml OpenCL::OpenCL - okFrontPanel::okFrontPanel + # okFrontPanel::okFrontPanel + nlohmann_json::nlohmann_json + fmt::fmt + xdaq::xdaq ) -if(UseMockOkFrontPanel) - find_package(fmt REQUIRED) - target_compile_definitions(IntanRHX PRIVATE UseMockOkFrontPanel) - target_link_libraries(IntanRHX PUBLIC fmt::fmt-header-only) -endif() - - # copy the bit file and kernel.cl add_custom_command(TARGET IntanRHX POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different diff --git a/Engine/API/Hardware/controller_info.cpp b/Engine/API/Hardware/controller_info.cpp new file mode 100644 index 0000000..1a840d3 --- /dev/null +++ b/Engine/API/Hardware/controller_info.cpp @@ -0,0 +1,53 @@ +#include "controller_info.h" + +#include + +#include +#include + +bool wait_xdaq(xdaq::Device *dev, int retry) +{ + using Clock = std::chrono::high_resolution_clock; + for (; retry >= 0; --retry) { + const auto start = Clock::now(); + while (true) { + if ((*dev->get_register_sync(0x22) & 0x4) == 0) return true; + if ((Clock::now() - start) > std::chrono::seconds(3)) break; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + if (retry == 0) return false; + dev->trigger(0x48, 3); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + return false; +} + +XDAQInfo read_xdaq_info(xdaq::Device *dev) +{ + if (!wait_xdaq(dev, 1)) { + throw std::runtime_error("XDAQ is not ready, please restart the device and try again"); + } + XDAQInfo info; + info.serial = fmt::format("{:08x}", *dev->get_register_sync(0x32)); + const auto hdmi = *dev->get_register_sync(0x31); + switch ((hdmi & 0xFF)) { + case 0: + if (((hdmi & 0xFF00) >> 8) == 1) + info.model = XDAQModel::Core; + else if (((hdmi & 0xFF00) >> 8) == 3) + info.model = XDAQModel::One; + else + info.model = XDAQModel::Unknown; + break; + case 1: info.model = XDAQModel::Core; break; + case 2: info.model = XDAQModel::One; break; + default: info.model = XDAQModel::Unknown; + } + info.expander = (*dev->get_register_sync(0x35)) != 0; + info.max_rhs_channels = ((hdmi >> 16) & 0xFF) * 16; + info.max_rhd_channels = ((hdmi >> 24) & 0xFF) * 32; + if (info.model == XDAQModel::Core) { + info.max_rhd_channels /= 2; + } + return info; +} \ No newline at end of file diff --git a/Engine/API/Hardware/controller_info.h b/Engine/API/Hardware/controller_info.h new file mode 100644 index 0000000..a9e5ac0 --- /dev/null +++ b/Engine/API/Hardware/controller_info.h @@ -0,0 +1,22 @@ +#pragma once +#include + +#include +#include + +enum class XDAQModel { Unknown = 0, Core = 1, One = 3 }; + +struct XDAQInfo { + std::string plugin; + std::string id; + std::string serial; + XDAQModel model = XDAQModel::Unknown; + + int max_rhd_channels = 0; + int max_rhs_channels = 0; + bool expander = false; + std::string device_config; + std::function get_device = nullptr; +}; + +XDAQInfo read_xdaq_info(xdaq::Device *dev); \ No newline at end of file diff --git a/Engine/API/Hardware/rhxcontroller.cpp b/Engine/API/Hardware/rhxcontroller.cpp index 9ad9a12..59ef8b1 100644 --- a/Engine/API/Hardware/rhxcontroller.cpp +++ b/Engine/API/Hardware/rhxcontroller.cpp @@ -28,16 +28,20 @@ // //------------------------------------------------------------------------------ -#include -#include -#include +#include +#include + #include -#include #include #include +#include +#include +#include +#include #include "rhxcontroller.h" #include "rhxregisters.h" +using json = nlohmann::json; // This class provides access to and control of one of the following: // (1) an Opal Kelly XEM6010 USB2/FPGA interface board running the Intan Rhythm interface Verilog code // (e.g., a 256-channel Intan RHD2000 USB Interface Board with 256-channel capacity) @@ -46,165 +50,23 @@ // (3) an Opal Kelly XEM6010 USB2/FPGA interface board running the Intan RhythmStim interface Verilog code // (e.g., an Intan Stim/Recording Controller with 128-channel capacity) -RHXController::RHXController(ControllerType type_, AmplifierSampleRate sampleRate_, bool is7310_) : - AbstractRHXController(type_, sampleRate_), - dev(nullptr), - is7310(is7310_), - previousDelay(-1) -{ -} - -RHXController::~RHXController() -{ - if (dev) delete dev; -} - -// List all available Opal Kelly devices currently connected to this computer. -// Return a vector of strings containing each device's serial number. -vector RHXController::listAvailableDeviceSerials() -{ - int i, nDevices; - okCFrontPanel *tempDev = new okCFrontPanel; - nDevices = tempDev->GetDeviceCount(); - - vector availableDevs; - for (i = 0; i < nDevices; ++i) { - availableDevs.push_back(tempDev->GetDeviceListSerial(i).c_str()); - } - delete tempDev; - return availableDevs; -} - -// Find an Opal Kelly board attached to a USB port with the given serial number and open it. -// Returns 1 if successful, -1 if FrontPanel cannot be loaded, and -2 if board can't be found. -int RHXController::open(const string& boardSerialNumber) -{ - dev = new okCFrontPanel; - cout << "Attempting to connect to device '" << boardSerialNumber.c_str() << "'\n"; - - okCFrontPanel::ErrorCode result = dev->OpenBySerial(boardSerialNumber); - // Attempt to open device. - if (result != okCFrontPanel::NoError) { - delete dev; - cerr << "Device could not be opened. Is one connected?\n"; - cerr << "Error = " << result << "\n"; - return -2; - } - - // Configure the on-board PLL appropriately. - dev->LoadDefaultPLLConfiguration(); - - // Get some general information about the XEM. - cout << "Opal Kelly device firmware version: " << dev->GetDeviceMajorVersion() << "." << - dev->GetDeviceMinorVersion() << '\n'; - cout << "Opal Kelly device serial number: " << dev->GetSerialNumber().c_str() << '\n'; - cout << "Opal Kelly device ID string: " << dev->GetDeviceID().c_str() << "\n\n"; - - return 1; -} - -// Find the first Opal Kelly board attached to a USB port and open it. -// Returns 1 if successful, -1 if FrontPanel cannot be loaded, and -2 if board can't be found. -int RHXController::open() +RHXController::RHXController(ControllerType type_, AmplifierSampleRate sampleRate_, xdaq::Device *dev,bool is7310_) + : AbstractRHXController(type_, sampleRate_), is7310(is7310_), previousDelay(-1),dev( + reinterpret_cast(dev) + ) { - dev = new okCFrontPanel; - string serialNumber = dev->GetDeviceListSerial(0).c_str(); - delete dev; - - return open(serialNumber); -} - -bool wait_xdaq(okCFrontPanel*dev, int retry){ - using Clock = std::chrono::high_resolution_clock; - for(; retry>=0; --retry){ - const auto start = Clock::now(); - while(true){ - dev->UpdateWireOuts(); - if( (dev->GetWireOutValue(0x22) & 0x4) == 0) return true; - if( (Clock::now() - start) > std::chrono::seconds(3) ) break; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - if(retry==0) return false; - dev->ActivateTriggerIn(0x48, 3); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } } -// Upload the configuration file (bitfile) to the FPGA. Return true if successful. -bool RHXController::uploadFPGABitfile(const string& filename) -{ - okCFrontPanel::ErrorCode errorCode = dev->ConfigureFPGA(filename); - switch (errorCode) { - case okCFrontPanel::NoError: - break; - case okCFrontPanel::DeviceNotOpen: - cerr << "FPGA configuration failed: Device not open.\n"; - return false; - case okCFrontPanel::FileError: - cerr << "FPGA configuration failed: Cannot find configuration file.\n"; - return false; - case okCFrontPanel::InvalidBitstream: - cerr << "FPGA configuration failed: Bitstream is not properly formatted.\n"; - return false; - case okCFrontPanel::DoneNotHigh: - cerr << "FPGA configuration failed: FPGA DONE signal did not assert after configuration.\n"; - cerr << "Note: Switch may be in PROM position instead of USB position.\n"; - return false; - case okCFrontPanel::TransferError: - cerr << "FPGA configuration failed: USB error occurred during download.\n"; - return false; - case okCFrontPanel::CommunicationError: - cerr << "FPGA configuration failed: Communication error with firmware.\n"; - return false; - case okCFrontPanel::UnsupportedFeature: - cerr << "FPGA configuration failed: Unsupported feature.\n"; - return false; - default: - cerr << "FPGA configuration failed: Unknown error.\n"; - return false; - } - - // Check for Opal Kelly FrontPanel support in the FPGA configuration. - if (dev->IsFrontPanelEnabled() == false) { - cerr << "Opal Kelly FrontPanel support is not enabled in this FPGA configuration.\n"; - delete dev; - return false; - } - - int boardId, boardVersion; - dev->UpdateWireOuts(); - boardId = dev->GetWireOutValue(WireOutBoardId); - boardVersion = dev->GetWireOutValue(WireOutBoardVersion); - - cout << "Rhythm configuration file successfully loaded. Rhythm version number: " << - boardVersion << "\n\n"; - - auto res = wait_xdaq(dev, 2); - if(!res){ - cerr << "FPGA configuration failed: XDAQ timeout, please try again.\n"; - return false; - } - return true; -} - -// Reset FPGA. This clears all auxiliary command RAM banks, clears the USB FIFO, and resets the per-channel sampling -// rate to 30.0 kS/s/ch. +// Reset FPGA. This clears all auxiliary command RAM banks, clears the USB FIFO, and resets the +// per-channel sampling rate to 30.0 kS/s/ch. void RHXController::resetBoard() { lock_guard lockOk(okMutex); - - resetBoard(dev); - - if (type == ControllerRecordUSB3 || type == ControllerStimRecord) { - // Set up USB3 block transfer parameters. - dev->SetWireInValue(WireInMultiUse, USB3BlockSize / 4); // Divide by 4 to convert from bytes to 32-bit words (used in FPGA FIFO) - dev->UpdateWireIns(); - dev->ActivateTriggerIn(TrigInConfig_USB3, 9); - dev->SetWireInValue(WireInMultiUse, RAMBurstSize); - dev->UpdateWireIns(); - dev->ActivateTriggerIn(TrigInConfig_USB3, 10); - } + dev->SetWireInValue(endPointWireInResetRun(), 0x01, 0x01); + dev->UpdateWireIns(); + dev->SetWireInValue(endPointWireInResetRun(), 0x00, 0x01); + dev->UpdateWireIns(); } // Initiate SPI data acquisition. @@ -239,34 +101,10 @@ bool RHXController::isRunning() void RHXController::flush() { lock_guard lockOk(okMutex); - - if (type == ControllerRecordUSB3 || type == ControllerStimRecord) { - dev->SetWireInValue(WireInResetRun, 1 << 16, 1 << 16); // override pipeout block throttle - dev->UpdateWireIns(); - - while (numWordsInFifo() >= usbBufferSize / BytesPerWord) { - dev->ReadFromBlockPipeOut(PipeOutData, USB3BlockSize, usbBufferSize, usbBuffer); - } - while (numWordsInFifo() > 0) { - dev->ReadFromBlockPipeOut(PipeOutData, USB3BlockSize, - USB3BlockSize * max(BytesPerWord * numWordsInFifo() / USB3BlockSize, (unsigned int)1), - usbBuffer); - } - - dev->SetWireInValue(WireInResetRun, 0 << 16, 1 << 16); - dev->UpdateWireIns(); - dev->SetWireInValue(WireInResetRun, 1 << 17, 1 << 17); - dev->UpdateWireIns(); - dev->SetWireInValue(WireInResetRun, 0 << 17, 1 << 17); - dev->UpdateWireIns(); - } else { - while (numWordsInFifo() >= usbBufferSize / BytesPerWord) { - dev->ReadFromPipeOut(PipeOutData, usbBufferSize, usbBuffer); - } - while (numWordsInFifo() > 0) { - dev->ReadFromPipeOut(PipeOutData, BytesPerWord * numWordsInFifo(), usbBuffer); - } - } + dev->SetWireInValue(WireInResetRun, 1 << 17, 1 << 17); + dev->UpdateWireIns(); + dev->SetWireInValue(WireInResetRun, 0 << 17, 1 << 17); + dev->UpdateWireIns(); } // Low-level FPGA reset. Call when closing application to make sure everything has stopped. @@ -274,7 +112,7 @@ void RHXController::resetFpga() { lock_guard lockOk(okMutex); - dev->ResetFPGA(); + // dev->ResetFPGA(); } // Read data block from the USB interface, if one is available. Return true if data block was available. @@ -331,9 +169,7 @@ bool RHXController::readDataBlocks(int numBlocks, deque &dataQueu if (type == ControllerRecordUSB3 || type == ControllerStimRecord) { result = dev->ReadFromBlockPipeOut(PipeOutData, USB3BlockSize, numBytesToRead, usbBuffer); - if (result == ok_Failed) { - cerr << "CRITICAL (readDataBlocks): Failure on pipe read. Check block and buffer sizes.\n"; - } else if (result == ok_Timeout) { + if (result <= 0) { cerr << "CRITICAL (readDataBlocks): Timeout on pipe read. Check block and buffer sizes.\n"; } } else { @@ -2032,7 +1868,7 @@ int RHXController::findConnectedChips(vector &chipType, vector &p } // Simple board reset -void RHXController::resetBoard(okCFrontPanel* dev_) +void RHXController::resetBoard(XDAQDeviceProxy* dev_) { dev_->SetWireInValue(endPointWireInResetRun(), 0x01, 0x01); dev_->UpdateWireIns(); @@ -2041,14 +1877,14 @@ void RHXController::resetBoard(okCFrontPanel* dev_) } // Return 4-bit "board mode" input. -int RHXController::getBoardMode(okCFrontPanel* dev_) +int RHXController::getBoardMode(XDAQDeviceProxy* dev_) { dev_->UpdateWireOuts(); return dev_->GetWireOutValue(endPointWireOutBoardMode()); } // Return number of SPI ports and if I/O expander board is present. -int RHXController::getNumSPIPorts(okCFrontPanel* dev_, bool isUSB3, bool& expanderBoardDetected, bool isRHS7310) +int RHXController::getNumSPIPorts(XDAQDeviceProxy* dev_, bool isUSB3, bool& expanderBoardDetected, bool isRHS7310) { bool spiPortPresent[8]; bool userId[3]; @@ -2066,8 +1902,6 @@ int RHXController::getNumSPIPorts(okCFrontPanel* dev_, bool isUSB3, bool& expand int WireInSerialDigitalInCntl = endPointWireInSerialDigitalInCntl(isUSB3); //int WireOutSerialDigitalIn = endPointWireOutSerialDigitalIn(isRHS7310 ? false : isUSB3); //int WireInSerialDigitalInCntl = endPointWireInSerialDigitalInCntl(isRHS7310 ? false : isUSB3); - if(!wait_xdaq(dev_, 3)) - throw std::runtime_error("Timeout waiting for board to be ready"); dev_->UpdateWireOuts(); expanderBoardDetected = (dev_->GetWireOutValue(0x35) & 0x04) != 0; @@ -2182,7 +2016,7 @@ void RHXController::forceAllDataStreamsOff() } // Manually pulse WireIns. -void RHXController::pulseWireIn(okCFrontPanel* dev_, int wireIn, unsigned int value) +void RHXController::pulseWireIn(XDAQDeviceProxy* dev_, int wireIn, unsigned int value) { dev_->SetWireInValue(wireIn, value); dev_->UpdateWireIns(); diff --git a/Engine/API/Hardware/rhxcontroller.h b/Engine/API/Hardware/rhxcontroller.h index 7561de2..5869fc8 100644 --- a/Engine/API/Hardware/rhxcontroller.h +++ b/Engine/API/Hardware/rhxcontroller.h @@ -34,29 +34,65 @@ #include "abstractrhxcontroller.h" #include "rhxglobals.h" #include "rhxdatablock.h" -#include "okFrontPanel.h" +#include +#include using namespace std; const int USB3BlockSize = 1024; const int RAMBurstSize = 32; +struct XDAQDeviceProxy : private xdaq::Device{ + int SetWireInValue(int ep, std::uint32_t value, std::uint32_t mask=xdaq::Device::value_mask){ + return this->set_register(ep, value, mask) == xdaq::BasicDeviceStatus::Success ? 0 : -1; + } + + int UpdateWireIns(){ + return this->send_registers() == xdaq::BasicDeviceStatus::Success ? 0 : -1; + } + + xdaq::Device::value_t GetWireOutValue(int ep){ + return this->get_register(ep); + } + + int UpdateWireOuts(){ + return this->read_registers() == xdaq::BasicDeviceStatus::Success ? 0 : -1; + } + + int ActivateTriggerIn(int ep, int bit){ + return this->trigger(ep, bit) == xdaq::BasicDeviceStatus::Success ? 0 : -1; + } + + long ReadFromBlockPipeOut(int epAddr, int blockSize, long length, unsigned char *data){ + return this->read(epAddr, length, data); + } + + long WriteToBlockPipeIn(int epAddr, int blockSize, long length, unsigned char *data){ + return this->write(epAddr, length, data); + } + + long WriteToPipeIn(int epAddr, long length, unsigned char *data){ + throw std::runtime_error("Not implemented"); + } + + long ReadFromPipeOut(int epAddr, long length, unsigned char *data){ + throw std::runtime_error("Not implemented"); + } + +}; + class RHXController : public AbstractRHXController { - public: - RHXController(ControllerType type_, AmplifierSampleRate sampleRate_, bool is7310_ = false); - ~RHXController(); + explicit RHXController(ControllerType type_, AmplifierSampleRate sampleRate_, xdaq::Device*dev, bool is7310_ = false); + ~RHXController() = default; bool isSynthetic() const override { return false; } bool isPlayback() const override { return false; } + int open(const string& boardSerialNumber) override { return 0; } + bool uploadFPGABitfile(const string& filename) override { return true; } AcquisitionMode acquisitionMode() const override { return LiveMode; } - vector listAvailableDeviceSerials(); - - int open(const string& boardSerialNumber) override; - int open(); - bool uploadFPGABitfile(const string& filename) override; void resetBoard() override; void run() override; @@ -125,9 +161,9 @@ class RHXController : public AbstractRHXController bool usePreviousDelay = false, int selectedPort = 0, int lastDetectedChip = -1) override; // Physical board only - static void resetBoard(okCFrontPanel* dev_); - static int getBoardMode(okCFrontPanel* dev_); - static int getNumSPIPorts(okCFrontPanel *dev_, bool isUSB3, bool& expanderBoardDetected, bool isRHS7310 = false); + static void resetBoard(XDAQDeviceProxy* dev_); + static int getBoardMode(XDAQDeviceProxy* dev_); + static int getNumSPIPorts(XDAQDeviceProxy* dev_, bool isUSB3, bool& expanderBoardDetected, bool isRHS7310 = false); void setVStimBus(int BusMode) override; private: @@ -135,7 +171,8 @@ class RHXController : public AbstractRHXController RHXController(const RHXController&); // declaration only RHXController& operator=(const RHXController&); // declaration only - okCFrontPanel *dev; + XDAQDeviceProxy* dev = nullptr; + bool is7310; // Opal Kelly module USB interface endpoint addresses common to all controller types @@ -276,7 +313,7 @@ class RHXController : public AbstractRHXController void forceAllDataStreamsOff() override; // Physical board only - static void pulseWireIn(okCFrontPanel* dev_, int wireIn, unsigned int value); + static void pulseWireIn(XDAQDeviceProxy* dev_, int wireIn, unsigned int value); static int endPointWireInResetRun() { return (int)WireInResetRun; } static int endPointWireInSerialDigitalInCntl(bool isUSB3); static int endPointWireOutSerialDigitalIn(bool isUSB3); diff --git a/Engine/CMakeLists.txt b/Engine/CMakeLists.txt index c4fbe1c..d95aca8 100644 --- a/Engine/CMakeLists.txt +++ b/Engine/CMakeLists.txt @@ -1,6 +1,8 @@ set(EngineSources Engine/API/Abstract/abstractrhxcontroller.cpp Engine/API/Abstract/abstractrhxcontroller.h + Engine/API/Hardware/controller_info.cpp + Engine/API/Hardware/controller_info.h Engine/API/Hardware/rhxcontroller.cpp Engine/API/Hardware/rhxcontroller.h Engine/API/Hardware/rhxdatablock.cpp @@ -68,7 +70,7 @@ set(EngineSources Engine/Processing/probemapdatastructures.h Engine/Processing/rhxdatareader.cpp Engine/Processing/rhxdatareader.h - Engine/Processing/semaphore.h + Engine/Processing/Semaphore.h Engine/Processing/signalsources.cpp Engine/Processing/signalsources.h Engine/Processing/softwarereferenceprocessor.cpp diff --git a/Engine/Processing/semaphore.h b/Engine/Processing/Semaphore.h similarity index 100% rename from Engine/Processing/semaphore.h rename to Engine/Processing/Semaphore.h diff --git a/Engine/Processing/datastreamfifo.h b/Engine/Processing/datastreamfifo.h index e0ffb77..c12306a 100644 --- a/Engine/Processing/datastreamfifo.h +++ b/Engine/Processing/datastreamfifo.h @@ -30,7 +30,7 @@ #ifndef DATASTREAMFIFO_H #define DATASTREAMFIFO_H -#include "semaphore.h" +#include "Semaphore.h" class DataStreamFifo { diff --git a/Engine/Processing/waveformfifo.h b/Engine/Processing/waveformfifo.h index 814e8fc..a942ee7 100644 --- a/Engine/Processing/waveformfifo.h +++ b/Engine/Processing/waveformfifo.h @@ -37,7 +37,7 @@ #include #include #include "rhxglobals.h" -#include "semaphore.h" +#include "Semaphore.h" #include "minmax.h" #include "signalsources.h" diff --git a/GUI/Dialogs/boardselectdialog.cpp b/GUI/Dialogs/boardselectdialog.cpp index 3822d84..3671e76 100644 --- a/GUI/Dialogs/boardselectdialog.cpp +++ b/GUI/Dialogs/boardselectdialog.cpp @@ -27,72 +27,54 @@ // See for documentation and product information. // //------------------------------------------------------------------------------ -#include + +#include "boardselectdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include +#include +#include +#include +#include +#include #include -#include "datafilereader.h" -#include "boardselectdialog.h" -#include "scrollablemessageboxdialog.h" -#include "advancedstartupdialog.h" -#include -#include -#include +#include #include +#include +#include +#include +#include +#include -// Check if FrontPanel DLL is loaded, and create an instance of okCFrontPanel. -BoardIdentifier::BoardIdentifier(QWidget *parent_) : - parent(parent_) -{ - qDebug() << "---- Intan Technologies ----\n"; - if (!okFrontPanel_TryLoadLib()) { -#ifdef _WIN32 - QMessageBox::warning(nullptr, "FrontPanel DLL could not be loaded.", "FrontPanel DLL could not be loaded. Make sure 'okFrontPanel.dll' is in the application start directory" - " and check that Microsoft Visual C++ 2010 Redistributable x64 is installed"); - qDebug() << "FrontPanel DLL could not be loaded. Make sure this DLL is in the application start directory."; -#elif __APPLE__ - QMessageBox::warning(nullptr, "FrontPanel DyLib could not be loaded.", "FrontPanel DyLib could not be loaded. Make sure 'libokFrontPanel.dylib' is in the Frameworks directory\n" - "of the application"); - qDebug() << "FrontPanel DyLib could not be loaded. Make sure DyLib can be found."; -#elif __linux__ - QMessageBox::warning(nullptr, "FrontPanel Shared Object could not be loaded.", "FrontPanel Shared Object could not be loaded. Make sure 'libokFrontPanel.so' is in the\n" - "application start directory."); - qDebug() << "FrontPanel Shared Object could not be loaded. Make sure the .so is in the application start directory"; -#endif - return; - } - qDebug() << "FrontPanel DLL loaded. Version: " << okFrontPanel_GetAPIVersionString(); - - dev = new okCFrontPanel; -} +#include "advancedstartupdialog.h" +#include "datafilereader.h" +#include "playbackrhxcontroller.h" +#include "rhxcontroller.h" +#include "rhxglobals.h" +#include "scrollablemessageboxdialog.h" +#include "startupdialog.h" +#include "syntheticrhxcontroller.h" +using json = nlohmann::json; -// Delete the controllers object. -BoardIdentifier::~BoardIdentifier() -{ - while (controllers.size() > 0) { - delete controllers.first(); - controllers.remove(0); - } -} +namespace fs = std::filesystem; -// Return a QString description of the specified board. -QString getBoardTypeString(const ControllerInfo& info) +class StackedWidget : public QStackedWidget { - std::stringstream ss; - switch(info.xdaqModel){ - case XDAQModel::Core: - ss << "XDAQ Core"; - break; - case XDAQModel::One: - ss << "XDAQ One"; - break; - default: - ss << "Unknown"; - break; - } - ss << "\nRHD " << info.maxRHDchannels << "ch"; - ss << "\nRHS " << info.maxRHSchannels << "ch"; - return QString::fromStdString(ss.str()); -} +public: + QSize sizeHint() const override { return currentWidget()->sizeHint(); } +}; // Return a QIcon with a picture of the specified board. QIcon getIcon(XDAQModel model, QStyle *style, int size) @@ -105,708 +87,505 @@ QIcon getIcon(XDAQModel model, QStyle *style, int size) return QIcon(style->standardIcon(QStyle::SP_MessageBoxQuestion).pixmap(size)); } -// Return a QVector of ControllerInfo structures containing information about each controller, after uploading bit files -// to each controller and determining its characteristics. -QVector BoardIdentifier::getConnectedControllersInfo() +// Return a QSize (that should be the minimum size of the table) which allows all columns to be +// visible, and up to 5 rows to be visible before a scroll bar is added. +QSize calculateTableSize(QTableWidget *boardTable) { - int i, nDevices; - qDebug() << "Scanning USB for Opal Kelly devices..."; - nDevices = dev->GetDeviceCount(); - qDebug() << "Found" << nDevices << "Opal Kelly" << ((nDevices == 1) ? "device" : "devices") << "connected."; - - for (i = 0; i < nDevices; ++i) { - // Create Controller data structure - ControllerInfo *controller = new ControllerInfo; - - // Fill Controller data structure with characteristics relating to this controller. - // This function uploads a bit file to the controller, writes some WireIns, and reads some WireOuts. - identifyController(controller, i); - - // Add this Controller to the 'controllers' QVector. - controllers.append(controller); - } - delete dev; - dev = nullptr; - - return controllers; -} - - -enum class XDAQHeadstageType{ - NA = 0, - Recording = 1, - StimRecord = 2 -}; - -XDAQHeadstageType askHeadstageType(){ - QMessageBox msgBox; - const auto msg = std::string("Select X-Headstage"); - msgBox.setText(QObject::tr(msg.c_str())); - auto* pButtonRHD = msgBox.addButton(QObject::tr("Record (X3R/X6R)"), QMessageBox::YesRole); - - auto* pButtonRHS = msgBox.addButton(QObject::tr("Stim-Record (X3SR)"), QMessageBox::NoRole); - auto cancel = msgBox.addButton(QObject::tr("Cancel"),QMessageBox::RejectRole); - msgBox.exec(); - if(msgBox.clickedButton() == cancel) exit(0); - return (msgBox.clickedButton() == pButtonRHD) ? XDAQHeadstageType::Recording : XDAQHeadstageType::StimRecord; -} - - -// Populate variables in 'controller'. Upload a bit file, writes some WireIns, and reads some WireOuts. -void BoardIdentifier::identifyController(ControllerInfo *controller, int index) -{ - // Populate serialNumber field. - controller->serialNumber = dev->GetDeviceListSerial(index).c_str(); - - // Upload bitfile to determine boardMode, expConnected, and numSPIPorts. - // Initialize expConnected, numSPIPorts, and boardMode to correspond to an unsuccessful mat. - controller->expConnected = false; - controller->numSPIPorts = 0; - controller->boardMode = UnknownUSB2Device; - controller->xdaqModel = XDAQModel::Unknown; - - // Open device. - if (dev->OpenBySerial(dev->GetDeviceListSerial(index).c_str()) != okCFrontPanel::NoError) { - qDebug() << "Device could not be opened. Is one connected?"; - return; - } - // Set up default PLL. - dev->LoadDefaultPLLConfiguration(); - - // Determine proper bitfile to load to FPGA (depending on if USB 2 or 3). - QString bitfilename = ConfigFileRHDController; - - if (!uploadFpgaBitfileQMessageBox(QCoreApplication::applicationDirPath() + "/" + bitfilename)) { - QMessageBox::critical(nullptr, QObject::tr("Configuration File Error: Software Aborting"), - QObject::tr("Cannot upload configuration file: ") + bitfilename + - QObject::tr(". Make sure file is in the same directory as the executable file.")); - exit(EXIT_FAILURE); + int width = boardTable->verticalHeader()->width() + 8; + for (int column = 0; column < boardTable->columnCount(); column++) { + width += boardTable->columnWidth(column) + 4; } - using Clock = std::chrono::high_resolution_clock; - const auto start = Clock::now(); - while(true){ - dev->UpdateWireOuts(); - if((dev->GetWireOutValue(0x22) & 4) == 0) break; - if(Clock::now() - start >= std::chrono::seconds(3)){ - qDebug() << "Timeout waiting for MCU"; - return; - } + // Make the minimum height to be 5 rows. + int numRows = 5; + if (boardTable->rowCount() <= 5) numRows = boardTable->rowCount(); + int height = boardTable->horizontalHeader()->height(); + for (int row = 0; row < numRows; row++) { + height += boardTable->rowHeight(row); } + height += 4; - RHXController::resetBoard(dev); - - // Read mode from board. - controller->numSPIPorts = RHXController::getNumSPIPorts(dev, true, controller->expConnected); - - dev->UpdateWireOuts(); - const uint32_t val = dev->GetWireOutValue(0x31); - const auto model = static_cast((val>>8) & 0xFF); - const auto rhd = 32 * (val >> 24) / (model == XDAQModel::Core ? 2 : 1); - const auto rhs = 16 * ((val >> 16) & 0xFF); - const uint32_t serial = dev->GetWireOutValue(0x32); - std::cout<xdaqModel = model; - controller->maxRHDchannels = rhd; - controller->maxRHSchannels = rhs; - controller->numSPIPorts = 0; - std::stringstream ss; - ss << std::setbase(16) << serial; - controller->xdaqSerial = QString::fromStdString(ss.str()); - controller->xdaqSerial = controller->xdaqSerial.toUpper(); - + return QSize(width, height); } -// Return name of Opal Kelly board based on model code. -QString BoardIdentifier::opalKellyModelName(int model) const +auto create_default_combobox(auto init, const auto &items, auto on_change) { - switch (model) { - case OK_PRODUCT_XEM3001V1: - return "XEM3001V1"; - case OK_PRODUCT_XEM3001V2: - return "XEM3001V2"; - case OK_PRODUCT_XEM3010: - return "XEM3010"; - case OK_PRODUCT_XEM3005: - return "XEM3005"; - case OK_PRODUCT_XEM3001CL: - return "XEM3001CL"; - case OK_PRODUCT_XEM3020: - return "XEM3020"; - case OK_PRODUCT_XEM3050: - return "XEM3050"; - case OK_PRODUCT_XEM9002: - return "XEM9002"; - case OK_PRODUCT_XEM3001RB: - return "XEM3001RB"; - case OK_PRODUCT_XEM5010: - return "XEM5010"; - case OK_PRODUCT_XEM6110LX45: - return "XEM6110LX45"; - case OK_PRODUCT_XEM6001: - return "XEM6001"; - case OK_PRODUCT_XEM6010LX45: - return "XEM6010LX45"; - case OK_PRODUCT_XEM6010LX150: - return "XEM6010LX150"; - case OK_PRODUCT_XEM6110LX150: - return "XEM6110LX150"; - case OK_PRODUCT_XEM6006LX9: - return "XEM6006LX9"; - case OK_PRODUCT_XEM6006LX16: - return "XEM6006LX16"; - case OK_PRODUCT_XEM6006LX25: - return "XEM6006LX25"; - case OK_PRODUCT_XEM5010LX110: - return "XEM5010LX110"; - case OK_PRODUCT_ZEM4310: - return "ZEM4310"; - case OK_PRODUCT_XEM6310LX45: - return "XEM6310LX45"; - case OK_PRODUCT_XEM6310LX150: - return "XEM6310LX150"; - case OK_PRODUCT_XEM6110V2LX45: - return "XEM6110V2LX45"; - case OK_PRODUCT_XEM6110V2LX150: - return "XEM6110V2LX150"; - case OK_PRODUCT_XEM6002LX9: - return "XEM6002LX9"; - case OK_PRODUCT_XEM6310MTLX45T: - return "XEM6310MTLX45T"; - case OK_PRODUCT_XEM6320LX130T: - return "XEM6320LX130T"; - case OK_PRODUCT_XEM7310A75: - return "XEM7310A75"; - default: - return "UNKNOWN"; - } + auto combo = new QComboBox(); + for (const auto &item : items) combo->addItem(item); + combo->setCurrentIndex(init); + QObject::connect(combo, QOverload::of(&QComboBox::currentIndexChanged), on_change); + return combo; } -bool uploadFpgaBitfileQMessageBox(const QString& filename, QWidget *parent, okCFrontPanel *dev) -{ - okCFrontPanel::ErrorCode errorCode = dev->ConfigureFPGA(filename.toStdString()); - - switch (errorCode) { - case okCFrontPanel::NoError: - break; - case okCFrontPanel::DeviceNotOpen: - QMessageBox::critical(parent, "FPGA configuration failed", "Device not open."); - return false; - case okCFrontPanel::FileError: - QMessageBox::critical(parent, "FPGA configuration failed", "Cannot find configuration file."); - return false; - case okCFrontPanel::InvalidBitstream: - QMessageBox::critical(parent, "FPGA configuration failed", "Bitstream is not properly formatted."); - return false; - case okCFrontPanel::DoneNotHigh: - QMessageBox::critical(parent, "FPGA configuration failed", "FPGA DONE signal did not assert after configuration. Make sure switch on Opal Kelly board is set to 'USB' not 'PROM'."); - return false; - case okCFrontPanel::TransferError: - QMessageBox::critical(parent, "FPGA configuration failed", "USB error occurred during download."); - return false; - case okCFrontPanel::CommunicationError: - QMessageBox::critical(parent, "FPGA configuration failed", "Communication error with firmware."); - return false; - case okCFrontPanel::UnsupportedFeature: - QMessageBox::critical(parent, "FPGA configuration failed", "Unsupported feature."); - return false; - default: - QMessageBox::critical(parent, "FPGA configuration failed", "Unknown error."); - return false; - } - - // Check for Opal Kelly FrontPanel support in the FPGA configuration. - if (dev->IsFrontPanelEnabled() == false) { - QMessageBox::critical(parent, "FPGA configuration failed", - "Opal Kelly FrontPanel support is not enabled in this FPGA configuration."); - return false; - } +auto create_default_sample_rate_checkbox = [](QWidget *parent) { + auto defaultSampleRateCheckBox = new QCheckBox(parent); + QSettings settings; + settings.beginGroup("XDAQ"); + defaultSampleRateCheckBox->setChecked(settings.value("useDefaultSettings", false).toBool()); + int defaultSampleRateIndex = settings.value("defaultSampleRate", 14).toInt(); + int defaultStimStepSizeIndex = settings.value("defaultStimStepSize", 6).toInt(); + defaultSampleRateCheckBox->setText( + parent->tr("Start software with ") + SampleRateString[defaultSampleRateIndex] + + parent->tr(" sample rate and ") + StimStepSizeString[defaultStimStepSizeIndex] + ); - return true; -} + QObject::connect(defaultSampleRateCheckBox, &QCheckBox::stateChanged, [](int state) { + // save the state of the checkbox to QSettings + QSettings settings; + settings.beginGroup("XDAQ"); + settings.setValue("useDefaultSettings", state == Qt::Checked); + settings.endGroup(); + }); + return defaultSampleRateCheckBox; +}; -// Upload bitfile specified by 'filename' to the FPGA, reporting any errors that occur as a QMessageBox. -bool BoardIdentifier::uploadFpgaBitfileQMessageBox(const QString& filename){ - return ::uploadFpgaBitfileQMessageBox(filename, parent, dev); -} +auto create_default_settings_file_checkbox = [](QWidget *parent) { + auto defaultSettingsFileCheckBox = new QCheckBox(parent); + QSettings settings; + settings.beginGroup("XDAQ"); + defaultSettingsFileCheckBox->setChecked( + settings.value("loadDefaultSettingsFile", false).toBool() + ); + auto defaultSettingsFile = QString(settings.value("defaultSettingsFile", "").toString()); + defaultSettingsFileCheckBox->setText( + parent->tr("Load default settings file: ") + defaultSettingsFile + ); + QObject::connect(defaultSettingsFileCheckBox, &QCheckBox::stateChanged, [](int state) { + QSettings settings; + settings.beginGroup("XDAQ"); + settings.setValue("loadDefaultSettingsFile", state == Qt::Checked); + settings.endGroup(); + }); + return defaultSettingsFileCheckBox; +}; -// Create a dialog window for user to select which board's software to initialize. -BoardSelectDialog::BoardSelectDialog(QWidget *parent) : - QDialog(parent), - boardTable(nullptr), - openButton(nullptr), - playbackButton(nullptr), - advancedButton(nullptr), - useOpenCL(true), - playbackPorts(255), - defaultSampleRateCheckBox(nullptr), - defaultSettingsFileCheckBox(nullptr), - splash(nullptr), - boardIdentifier(nullptr), - dataFileReader(nullptr), - rhxController(nullptr), - state(nullptr), - controllerInterface(nullptr), - parser(nullptr), - controlWindow(nullptr) +auto get_properties_table(QStringList headers, std::vector> rows) { - // Information used by QSettings to save basic settings across sessions. - QCoreApplication::setOrganizationName(OrganizationName); - QCoreApplication::setOrganizationDomain(OrganizationDomain); - QCoreApplication::setApplicationName(ApplicationName); - - // Globally disable unused Context Help buttons from windows/dialogs - QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); - - // Initialize Board Identifier. - boardIdentifier = new BoardIdentifier(this); - - // Determine how many and what type of controllers are connected to this machine. - controllersInfo = boardIdentifier->getConnectedControllersInfo(); - - // Create a table containing information about connected controllers. - boardTable = new QTableWidget(controllersInfo.size(), 3, this); - populateTable(); - - // Allow the user to open the selected board by clicking the open button - // (can also be done by double-clicking the row in the table). - openButton = new QPushButton(tr("Open"), this); - openButton->setEnabled(false); - connect(openButton, SIGNAL(clicked()), this, SLOT(openSelectedBoard())); - - // Allow the user to open a data file for playback. - playbackButton = new QPushButton(tr("Data File Playback"), this); - connect(playbackButton, SIGNAL(clicked()), this, SLOT(playbackDataFile())); - - // Allow the user to open 'Advanced' dialog to allow opting out of OpenCL - advancedButton = new QPushButton(tr("Advanced"), this); - connect(advancedButton, SIGNAL(clicked()), this, SLOT(advanced())); - int advancedButtonSize = advancedButton->sizeHint().width() + 10; - advancedButton->setFixedWidth(advancedButtonSize); - - defaultSampleRateCheckBox = new QCheckBox(this); - defaultSettingsFileCheckBox = new QCheckBox(this); - - QHBoxLayout *firstRowLayout = new QHBoxLayout; - firstRowLayout->addWidget(defaultSettingsFileCheckBox); - firstRowLayout->addStretch(1); - firstRowLayout->addWidget(playbackButton); - - QHBoxLayout *secondRowLayout = new QHBoxLayout; - secondRowLayout->addWidget(defaultSampleRateCheckBox); - secondRowLayout->addStretch(1); - secondRowLayout->addWidget(openButton); - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->addWidget(boardTable); - mainLayout->addLayout(firstRowLayout); - mainLayout->addLayout(secondRowLayout); - mainLayout->addWidget(advancedButton); - - setWindowTitle("Select XDAQ"); - - setLayout(mainLayout); - - resize(minimumSize()); - - splash = new QSplashScreen(QPixmap(":images/RHX_splash.png")); - splashMessage = "Copyright " + CopyrightSymbol + " " + ApplicationCopyrightYear + " Intan Technologies. RHX version " + - SoftwareVersion + ". Opening XDAQ ..."; - splashMessageAlign = Qt::AlignCenter | Qt::AlignBottom; - splashMessageColor = Qt::white; - - if (!validControllersPresent(controllersInfo)) { - showDemoMessageBox(); - } else { - show(); - } - - // Highlight first enabled row. - for (int row = 0; row < boardTable->rowCount(); row++) { - // Get this row's text. - QString thisText = boardTable->itemAt(row, 0)->text(); - // If this type of board is recognized and enabled, give it focus. Otherwise, move to the next row. - if (thisText != "N/A") { - boardTable->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, 2), true); - boardTable->setFocus(); - break; + auto num_rows = rows.size(); + auto num_cols = rows[0].size(); + auto table = new QTableWidget(num_rows, num_cols, nullptr); + table->setHorizontalHeaderLabels(headers); + table->horizontalHeader()->setSectionsClickable(false); + table->verticalHeader()->hide(); + for (int r = 0; r < num_rows; ++r) { + for (int c = 0; c < num_cols; ++c) { + table->setCellWidget(r, c, rows[r][c]); + rows[r][c]->setContentsMargins(2, 0, 2, 0); } } + table->resizeColumnsToContents(); + table->resizeRowsToContents(); + table->setMinimumSize(calculateTableSize(table)); + table->horizontalHeader()->setStretchLastSection(true); + return table; } -BoardSelectDialog::~BoardSelectDialog() -{ - if (boardIdentifier) delete boardIdentifier; - if (dataFileReader) delete dataFileReader; - if (controllerInterface) delete controllerInterface; - if (rhxController) delete rhxController; - if (parser) delete parser; -// if (state) delete state; // This causes "The program has unexpectedly finished" upon quitting for some unknown reason. -} - -// Determine whether or not the given QVector of type ControllerInfo contains any valid controllers that can be opened -// by this software (RHD USB interface board, RHD Recording Controller, or RHS Stim/Record Controller). -bool BoardSelectDialog::validControllersPresent(QVector cInfo) +auto get_playback_board(QWidget *parent, auto launch) { - for (int i = 0; i < cInfo.size(); i++){ - std::cout<(cInfo[i]->xdaqModel)<<'\n'; - if(cInfo[i]->xdaqModel != XDAQModel::Unknown) - return true; - } - return false; -} + QSettings settings; + settings.beginGroup("XDAQ"); + auto last_playback_file = settings.value("lastPlaybackFile", "").toString(); + auto last_playback_ports = settings.value("playbackPorts", 255).toUInt(); + settings.endGroup(); -void BoardSelectDialog::showDemoMessageBox() -{ - AmplifierSampleRate sampleRate = SampleRate20000Hz; - StimStepSize stimStepSize = StimStepSize500nA; - bool rememberSettings = false; + auto app_icon = new QTableWidgetItem( + parent->style()->standardIcon(QStyle::SP_MediaPlay).pixmap(40), QObject::tr("Playback") + ); - DemoSelections demoSelection; - DemoDialog demoDialog(&demoSelection, useOpenCL, playbackPorts, this); - demoDialog.exec(); + auto open_file_button = new QPushButton( + parent->style()->standardIcon(QStyle::SP_DialogOpenButton).pixmap(20), + last_playback_file.isEmpty() ? QObject::tr("Select Playback Data File") : last_playback_file + ); - if (demoSelection == DemoPlayback) { - playbackDataFile(); - } else { - ControllerType controllerType; - if (demoSelection == DemoUSBInterfaceBoard) { - controllerType = ControllerRecordUSB2; - } else if (demoSelection == DemoRecordingController) { - controllerType = ControllerRecordUSB3; - } else { - controllerType = ControllerStimRecord; + auto launch_button = new QPushButton( + parent->style()->standardIcon(QStyle::SP_MediaPlay).pixmap(20), parent->tr("Launch") + ); + launch_button->setEnabled(!last_playback_file.isEmpty()); + // boardTable->setCellWidget(row, 2, launch_button); + + QObject::connect( + open_file_button, + &QPushButton::clicked, + [parent, open_file_button, launch_button]() { + QSettings settings; + settings.beginGroup("XDAQ"); + QString playbackFileName = QFileDialog::getOpenFileName( + nullptr, + parent->tr("Select Intan Data File"), + settings.value("playbackDirectory", ".").toString(), // default directory + parent->tr("Intan Data Files (*.rhd *.rhs)") + ); + if (!playbackFileName.isEmpty()) { + settings.setValue( + "playbackDirectory", + fs::path(playbackFileName.toStdString()).parent_path().string().c_str() + ); + settings.setValue("lastPlaybackFile", playbackFileName); + } + settings.endGroup(); + open_file_button->setText(playbackFileName); + launch_button->setEnabled(!playbackFileName.isEmpty()); } + ); - StartupDialog startupDialog(controllerType, &sampleRate, &stimStepSize, &rememberSettings, false, false, this); - startupDialog.exec(); - - splash->show(); - splash->showMessage(splashMessage, splashMessageAlign, splashMessageColor); - - startSoftware(controllerType, sampleRate, stimStepSize, (controllerType == ControllerRecordUSB3) ? 8 : 4, true, "N/A", - SyntheticMode, false, nullptr, nullptr); - - splash->finish(controlWindow); - this->accept(); + std::shared_ptr launch_properties = + std::shared_ptr(new json{{"playbackPorts", last_playback_ports}}); + + std::vector> rows; + for (int port = 0; port < 8; ++port) { + auto label = new QLabel(parent->tr(fmt::format("Port {}", (char) ('A' + port)).c_str())); + auto enable_checkbox = new QCheckBox(); + rows.push_back( + {label, + enable_checkbox, + new QLabel( + parent->tr(fmt::format("Enable playback on port {}", (char) ('A' + port)).c_str()) + )} + ); + enable_checkbox->setChecked((last_playback_ports & (1 << port)) > 0); + + QObject::connect( + enable_checkbox, + &QCheckBox::stateChanged, + [launch_properties, port](int state) { + QSettings settings; + settings.beginGroup("XDAQ"); + auto ports = settings.value("playbackPorts", 255).toUInt(); + if (state == Qt::Checked) { + ports |= (1 << port); + } else { + ports &= ~(1 << port); + } + settings.setValue("playbackPorts", ports); + settings.endGroup(); + launch_properties->at("playbackPorts") = ports; + } + ); } -} - -// Fill the table with information corresponding to all connected Opal Kelly devices. -void BoardSelectDialog::populateTable() -{ - // Set up header. - boardTable->setHorizontalHeaderLabels(QStringList() << "XDAQ" << "I/O Expander" << "Serial Number"); - boardTable->horizontalHeader()->setSectionsClickable(false); - boardTable->verticalHeader()->setSectionsClickable(false); - boardTable->setFocusPolicy(Qt::ClickFocus); - - // Populate each row with information corresponding to a single controller. - Qt::ItemFlags itemFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; - for (int row = 0; row < controllersInfo.size(); row++) { - const auto& info = *controllersInfo[row]; - // Report the type of board. - QString boardType = getBoardTypeString(info); - auto* intanBoardType = new QTableWidgetItem(getIcon(info.xdaqModel, style(), 100), boardType); - // If this type is clamp, add a description to the boardType string. - if (info.boardMode == CLAMPController) { - intanBoardType->setText(intanBoardType->text().append(tr(" (run Clamp software to use)"))); - } - intanBoardType->setFlags(itemFlags); - boardTable->setItem(row, 0, intanBoardType); - - // Report if an io expander is connected. - QTableWidgetItem *ioExpanderStatus = nullptr; - if (info.xdaqModel == XDAQModel::Unknown) { - ioExpanderStatus = new QTableWidgetItem(tr("N/A")); - } else { - QIcon icon((controllersInfo[row]->expConnected) ? - style()->standardIcon(QStyle::SP_DialogYesButton).pixmap(20) : - style()->standardIcon(QStyle::SP_DialogNoButton).pixmap(20)); - ioExpanderStatus = new QTableWidgetItem(icon, (controllersInfo[row]->expConnected ? - tr("I/O Expander Connected") : - tr("No I/O Expander Connected"))); - } - ioExpanderStatus->setFlags(itemFlags); - boardTable->setItem(row, 1, ioExpanderStatus); - - // Report the serial number of this board. - QTableWidgetItem *serialNumber = new QTableWidgetItem(controllersInfo[row]->xdaqSerial); - serialNumber->setFlags(itemFlags); - boardTable->setItem(row, 2, serialNumber); - - // If the type of board is unrecognized, disable the row (greyed-out and unclickable). - if (info.xdaqModel == XDAQModel::Unknown) { - intanBoardType->setFlags(Qt::NoItemFlags); - ioExpanderStatus->setFlags(Qt::NoItemFlags); - serialNumber->setFlags(Qt::NoItemFlags); + auto launch_properties_widget = get_properties_table( + {parent->tr("Property"), parent->tr("Value"), parent->tr("Description")}, rows + ); + auto launch_button_layout = new QVBoxLayout; + launch_button_layout->addWidget(launch_button, 0, Qt::AlignLeft); + launch_button_layout->addWidget(new QLabel(parent->tr("Launch Properties"))); + launch_button_layout->addWidget(launch_properties_widget); + auto launch_button_widget = new QWidget(); + launch_button_widget->setLayout(launch_button_layout); + + QObject::connect( + launch_button, + &QPushButton::clicked, + [parent, open_file_button, launch_button, launch]() { + auto playbackFileName = open_file_button->text(); + if (!playbackFileName.isEmpty()) { + QSettings settings; + settings.beginGroup("XDAQ"); + bool canReadFile = true; + QString report; + auto dataFileReader = new DataFileReader( + playbackFileName, + canReadFile, + report, + settings.value("playbackPorts", 255).toUInt() + ); + + if (!canReadFile) { + ScrollableMessageBoxDialog msgBox(nullptr, "Unable to Load Data File", report); + msgBox.exec(); + settings.endGroup(); + return; + } else if (!report.isEmpty()) { + ScrollableMessageBoxDialog msgBox(nullptr, "Data File Loaded", report); + msgBox.exec(); + QFileInfo fileInfo(playbackFileName); + settings.setValue("playbackDirectory", fileInfo.absolutePath()); + } + settings.endGroup(); + + launch( + new PlaybackRHXController( + dataFileReader->controllerType(), + dataFileReader->sampleRate(), + dataFileReader + ), + dataFileReader + ); + } } - } - - // Make table visible in full (for up to 5 rows... then allow a scroll bar to be used). - boardTable->setIconSize(QSize(283, 100)); - boardTable->resizeColumnsToContents(); - boardTable->resizeRowsToContents(); - boardTable->setMinimumSize(calculateTableSize()); - boardTable->setSelectionBehavior(QAbstractItemView::SelectRows); - boardTable->setSelectionMode(QAbstractItemView::SingleSelection); - - connect(boardTable, SIGNAL(cellDoubleClicked(int, int)), - this, SLOT(startBoard(int))); // When the user double clicks a row, trigger that board's software. - connect(boardTable, SIGNAL(currentCellChanged(int, int, int, int)), - this, SLOT(newRowSelected(int))); // When the user selects a valid row, enable 'open' button. -} - -// Return a QSize (that should be the minimum size of the table) which allows all columns to be visible, and up to 5 rows -// to be visible before a scroll bar is added. -QSize BoardSelectDialog::calculateTableSize() -{ - int width = boardTable->verticalHeader()->width(); - for (int column = 0; column < boardTable->columnCount(); column++) { - width += boardTable->columnWidth(column); - } - width += 4; - - // Make the minimum height to be 5 rows. - int numRows = 5; - if (boardTable->rowCount() <= 5) - numRows = boardTable->rowCount(); - int height = boardTable->horizontalHeader()->height(); - for (int row = 0; row < numRows; row++) { - height += boardTable->rowHeight(row); - } - height += 4; + ); - return QSize(width, height); + return std::make_tuple(app_icon, open_file_button, launch_button_widget, launch_properties); } -void BoardSelectDialog::startSoftware(ControllerType controllerType, AmplifierSampleRate sampleRate, StimStepSize stimStepSize, - int numSPIPorts, bool expanderConnected, const QString& boardSerialNumber, - AcquisitionMode mode, bool is7310, DataFileReader* dataFileReader, const ControllerInfo* info) +auto get_xdaq_board(QWidget *parent, auto launch, const XDAQInfo &info) { - if (mode == LiveMode) { - rhxController = new RHXController(controllerType, sampleRate, is7310); - } else if (mode == SyntheticMode) { - rhxController = new SyntheticRHXController(controllerType, sampleRate); - } else if (mode == PlaybackMode) { - rhxController = new PlaybackRHXController(controllerType, sampleRate, dataFileReader); + auto app_icon = new QTableWidgetItem( + getIcon(info.model, parent->style(), info.model == XDAQModel::Unknown ? 40 : 80), + QString::fromStdString(info.id) + ); + auto layout = new QVBoxLayout; + layout->addWidget(new QLabel(QString::fromStdString(info.plugin))); + // Report if an io expander is connected. + if (info.model == XDAQModel::Unknown) { + layout->addWidget(new QLabel(parent->tr("N/A"))); + } else if (info.expander) { + auto expander_layout = new QHBoxLayout; + auto icon = new QLabel(); + icon->setPixmap(parent->style()->standardIcon(QStyle::SP_DialogYesButton).pixmap(20)); + expander_layout->addWidget(icon); + expander_layout->addWidget(new QLabel(parent->tr("I/O Expander Connected"))); + layout->addLayout(expander_layout); } else { - return; + auto expander_layout = new QHBoxLayout; + auto icon = new QLabel(); + icon->setPixmap(parent->style()->standardIcon(QStyle::SP_DialogNoButton).pixmap(20)); + expander_layout->addWidget(icon); + expander_layout->addWidget(new QLabel(parent->tr("No I/O Expander Connected"))); + layout->addLayout(expander_layout); } - - QSettings settings; - bool testMode = (settings.value("chipTestMode", "").toString() == "Intan Chip Test Mode") && mode == LiveMode; - state = new SystemState(rhxController, stimStepSize, - numSPIPorts, expanderConnected, testMode, dataFileReader, - info == nullptr ? false : info->xdaqModel == XDAQModel::One, - info == nullptr ? 2 : (info->xdaqModel == XDAQModel::One ? 2 : 1) + // Report the serial number of this board. + auto serial_layout = new QHBoxLayout; + serial_layout->addWidget(new QLabel(parent->tr("Serial Number"))); + serial_layout->addWidget(new QLabel(QString::fromStdString(info.serial))); + layout->addLayout(serial_layout); + auto device_widget = new QWidget(); + device_widget->setLayout(layout); + device_widget->setDisabled(info.model == XDAQModel::Unknown); + + std::shared_ptr launch_properties = + std::shared_ptr(new json{{"sample_rate", SampleRate30000Hz}, {"stim_step_size", 5}}); + auto launch_button_rhd = new QPushButton(parent->tr("Record (X3R/X6R)")); + QObject::connect(launch_button_rhd, &QPushButton::clicked, [launch, info, launch_properties]() { + QSettings settings; + settings.beginGroup("XDAQ"); + launch( + new RHXController( + ControllerType::ControllerRecordUSB3, + launch_properties->at("sample_rate"), + info.get_device(info.device_config) + ), + launch_properties->at("stim_step_size") + ); + settings.endGroup(); + }); + auto launch_button_rhs = new QPushButton(parent->tr("Stim-Record (X3SR)")); + QObject::connect(launch_button_rhs, &QPushButton::clicked, [launch, info, launch_properties]() { + QSettings settings; + settings.beginGroup("XDAQ"); + launch( + new RHXController( + ControllerType::ControllerStimRecord, + launch_properties->at("sample_rate"), + info.get_device(info.device_config) + ), + launch_properties->at("stim_step_size") + ); + settings.endGroup(); + }); + auto sr_selector = create_default_combobox( + SampleRate30000Hz, + SampleRateString, + [launch_properties](int index) { launch_properties->at("sample_rate") = index; } + ); + auto stim_step_selector = create_default_combobox( + StimStepSize200nA, + StimStepSizeString, + [launch_properties](int index) { launch_properties->at("stim_step_size") = index; } ); - state->highDPIScaleFactor = this->devicePixelRatio(); // Use this to adjust graphics for high-DPI monitors. - state->availableScreenResolution = QGuiApplication::primaryScreen()->geometry(); - controllerInterface = new ControllerInterface(state, rhxController, boardSerialNumber, useOpenCL, dataFileReader, this, is7310); - state->setupGlobalSettingsLoadSave(controllerInterface); - parser = new CommandParser(state, controllerInterface, this); - controlWindow = new ControlWindow(state, parser, controllerInterface, rhxController); - parser->controlWindow = controlWindow; - - connect(controlWindow, SIGNAL(sendExecuteCommand(QString)), parser, SLOT(executeCommandSlot(QString))); - connect(controlWindow, SIGNAL(sendExecuteCommandWithParameter(QString,QString)), parser, SLOT(executeCommandWithParameterSlot(QString, QString))); - connect(controlWindow, SIGNAL(sendGetCommand(QString)), parser, SLOT(getCommandSlot(QString))); - connect(controlWindow, SIGNAL(sendSetCommand(QString, QString)), parser, SLOT(setCommandSlot(QString, QString))); - - connect(parser, SIGNAL(stimTriggerOn(QString)), controllerInterface, SLOT(manualStimTriggerOn(QString))); - connect(parser, SIGNAL(stimTriggerOff(QString)), controllerInterface, SLOT(manualStimTriggerOff(QString))); - connect(parser, SIGNAL(stimTriggerPulse(QString)), controllerInterface, SLOT(manualStimTriggerPulse(QString))); - - connect(parser, SIGNAL(updateGUIFromState()), controlWindow, SLOT(updateFromState())); - connect(parser, SIGNAL(sendLiveNote(QString)), controllerInterface->saveThread(), SLOT(saveLiveNote(QString))); - - connect(controllerInterface, SIGNAL(TCPErrorMessage(QString)), parser, SLOT(TCPErrorSlot(QString))); - - if (dataFileReader) { - connect(controlWindow, SIGNAL(setDataFileReaderSpeed(double)), dataFileReader, SLOT(setPlaybackSpeed(double))); - connect(controlWindow, SIGNAL(setDataFileReaderLive(bool)), dataFileReader, SLOT(setLive(bool))); - connect(controlWindow, SIGNAL(jumpToEnd()), dataFileReader, SLOT(jumpToEnd())); - connect(controlWindow, SIGNAL(jumpToStart()), dataFileReader, SLOT(jumpToStart())); - connect(controlWindow, SIGNAL(jumpToPosition(QString)), dataFileReader, SLOT(jumpToPosition(QString))); - connect(controlWindow, SIGNAL(jumpRelative(double)), dataFileReader, SLOT(jumpRelative(double))); - connect(controlWindow, SIGNAL(setStatusBarReadyPlayback()), dataFileReader, SLOT(setStatusBarReady())); - connect(dataFileReader, SIGNAL(setStatusBar(QString)), controlWindow, SLOT(updateStatusBar(QString))); - connect(dataFileReader, SIGNAL(setTimeLabel(QString)), controlWindow, SLOT(updateTimeLabel(QString))); - connect(dataFileReader, SIGNAL(sendSetCommand(QString,QString)), parser, SLOT(setCommandSlot(QString,QString))); - } - connect(controllerInterface, SIGNAL(haveStopped()), controlWindow, SLOT(stopAndReportAnyErrors())); - connect(controllerInterface, SIGNAL(setTimeLabel(QString)), controlWindow, SLOT(updateTimeLabel(QString))); - connect(controllerInterface, SIGNAL(setTopStatusLabel(QString)), controlWindow, SLOT(updateTopStatusLabel(QString))); - connect(controllerInterface, SIGNAL(setHardwareFifoStatus(double)), controlWindow, SLOT(updateHardwareFifoStatus(double))); - connect(controllerInterface, SIGNAL(cpuLoadPercent(double)), controlWindow, SLOT(updateMainCpuLoad(double))); - - connect(controllerInterface->saveThread(), SIGNAL(setStatusBar(QString)), controlWindow, SLOT(updateStatusBar(QString))); - connect(controllerInterface->saveThread(), SIGNAL(setTimeLabel(QString)), controlWindow, SLOT(updateTimeLabel(QString))); - connect(controllerInterface->saveThread(), SIGNAL(sendSetCommand(QString, QString)), - parser, SLOT(setCommandSlot(QString, QString))); - connect(controllerInterface->saveThread(), SIGNAL(error(QString)), controlWindow, SLOT(queueErrorMessage(QString))); - - controlWindow->show(); - - settings.beginGroup(ControllerTypeSettingsGroup[(int)state->getControllerTypeEnum()]); - if (defaultSettingsFileCheckBox->isChecked()) { - settings.setValue("loadDefaultSettingsFile", true); - QString defaultSettingsFile = QString(settings.value("defaultSettingsFile", "").toString()); - if (controlWindow->loadSettingsFile(defaultSettingsFile)) { - emit controlWindow->setStatusBar("Loaded default settings file " + defaultSettingsFile); - } else { - emit controlWindow->setStatusBar("Error loading default settings file " + defaultSettingsFile); - } - } else { - settings.setValue("loadDefaultSettingsFile", false); - } - settings.endGroup(); + auto launch_properties_widget = get_properties_table( + {parent->tr("Property"), parent->tr("Value")}, + {{new QLabel(parent->tr("Sample Rate")), sr_selector - if (state->testMode->getValue()) { - state->plottingMode->setValue("Original"); - } -} + }, + {new QLabel(parent->tr("Stim Step Size")), stim_step_selector}} + ); -// Trigger the currently selected board's software. -void BoardSelectDialog::openSelectedBoard() -{ - QTableWidgetItem *selectedItem = boardTable->selectedItems().first(); - startBoard(selectedItem->row()); + + auto launch_button_layout = new QVBoxLayout; + launch_button_layout->addWidget(launch_button_rhd, 0, Qt::AlignLeft); + launch_button_layout->addWidget(launch_button_rhs, 0, Qt::AlignLeft); + launch_button_layout->addWidget(launch_properties_widget); + // launch_button_layout->setEnabled(info.model != XDAQModel::Unknown); + auto launch_button_widget = new QWidget(); + launch_button_widget->setLayout(launch_button_layout); + return std::make_tuple(app_icon, device_widget, launch_button_widget, launch_properties); } -// Enable the 'Open' button when a valid controller's row is selected. -void BoardSelectDialog::newRowSelected(int row) +auto get_demo_board(QWidget *parent, auto launch) { - openButton->setEnabled(true); + auto app_icon = new QTableWidgetItem( + parent->style()->standardIcon(QStyle::SP_ComputerIcon).pixmap(40), parent->tr("Demo") + ); - QSettings settings; - settings.beginGroup("XDAQ"); + std::shared_ptr launch_properties = std::shared_ptr(new json{ + {"sample_rate", SampleRate30000Hz}, + {"stim_step_size", StimStepSize200nA}, + }); - if (settings.value("useDefaultSettings", false).toBool()) { - defaultSampleRateCheckBox->setChecked(true); - defaultSampleRateCheckBox->setVisible(true); - int defaultSampleRateIndex = settings.value("defaultSampleRate", 14).toInt(); - int defaultStimStepSizeIndex = settings.value("defaultStimStepSize", 6).toInt(); - defaultSampleRateCheckBox->setText(tr("Start software with ") + SampleRateString[defaultSampleRateIndex] + - tr(" sample rate and ") + StimStepSizeString[defaultStimStepSizeIndex]); - } else { - defaultSampleRateCheckBox->setChecked(false); - defaultSampleRateCheckBox->setVisible(false); - } + auto sr_selector = create_default_combobox( + launch_properties->at("sample_rate"), + SampleRateString, + [launch_properties](int index) { launch_properties->at("sample_rate") = index; } + ); + auto stim_step_selector = create_default_combobox( + launch_properties->at("stim_step_size"), + StimStepSizeString, + [launch_properties](int index) { launch_properties->at("stim_step_size") = index; } + ); - if (settings.value("loadDefaultSettingsFile", false).toBool()) { - defaultSettingsFileCheckBox->setChecked(true); - defaultSettingsFileCheckBox->setVisible(true); - QString defaultSettingsFile = QString(settings.value("defaultSettingsFile", "").toString()); - defaultSettingsFileCheckBox->setText(tr("Load default settings file: ") + defaultSettingsFile); - } else { - defaultSettingsFileCheckBox->setChecked(false); - defaultSettingsFileCheckBox->setVisible(false); - } + auto launch_properties_widget = get_properties_table( + {parent->tr("Property"), parent->tr("Value")}, + {{new QLabel(parent->tr("Sample Rate")), sr_selector}, + {new QLabel(parent->tr("Stim Step Size")), stim_step_selector}} + ); - settings.endGroup(); + auto launch_button_rhd = new QPushButton(parent->tr("Record (X3R/X6R) Demo")); + QObject::connect(launch_button_rhd, &QPushButton::clicked, [launch, launch_properties]() { + QSettings settings; + settings.beginGroup("XDAQ"); + launch( + new SyntheticRHXController(ControllerType::ControllerRecordUSB3, SampleRate30000Hz), + launch_properties->at("stim_step_size") + ); + settings.endGroup(); + }); + auto launch_button_rhs = new QPushButton(parent->tr("Stim-Record (X3SR) Demo")); + QObject::connect(launch_button_rhs, &QPushButton::clicked, [launch, launch_properties]() { + QSettings settings; + settings.beginGroup("XDAQ"); + launch( + new SyntheticRHXController(ControllerType::ControllerStimRecord, SampleRate30000Hz), + launch_properties->at("stim_step_size") + ); + settings.endGroup(); + }); + auto launch_button_layout = new QVBoxLayout; + launch_button_layout->addWidget(launch_button_rhd, 0, Qt::AlignLeft); + launch_button_layout->addWidget(launch_button_rhs, 0, Qt::AlignLeft); + launch_button_layout->addWidget(launch_properties_widget); + auto launch_button_widget = new QWidget(); + launch_button_widget->setLayout(launch_button_layout); + // boardTable->setCellWidget(row, 2, launch_button_widget); + return std::make_tuple(app_icon, new QWidget(), launch_button_widget, launch_properties); } -// Trigger the given row's board's software. -void BoardSelectDialog::startBoard(int row) +// Create a dialog window for user to select which board's software to initialize. +BoardSelectDialog::BoardSelectDialog(QWidget *parent, const std::vector &xdaq_infos) + : QDialog(parent) { - openButton->setEnabled(false); - playbackButton->setEnabled(false); - boardTable->setEnabled(false); - - AmplifierSampleRate sampleRate = SampleRate20000Hz; - StimStepSize stimStepSize = StimStepSize500nA; - bool rememberSettings = false; - - ControllerType controllerType = ControllerRecordUSB3; - auto headstagetype = askHeadstageType(); - switch(headstagetype){ - case XDAQHeadstageType::Recording: - controllerType = ControllerRecordUSB3; - break; - case XDAQHeadstageType::StimRecord: - controllerType = ControllerStimRecord; - break; - default: - break; - } - auto info = controllersInfo[row]; - info->numSPIPorts = headstagetype == XDAQHeadstageType::StimRecord ? 4 : 8; + auto launch_panel = new StackedWidget(); + auto boardTable = new QTableWidget(0, 2, nullptr); + // Set up header. + boardTable->setHorizontalHeaderLabels({tr("App"), tr("Info")}); + boardTable->horizontalHeader()->setSectionsClickable(false); + boardTable->verticalHeader()->setSectionsClickable(false); + boardTable->setFocusPolicy(Qt::ClickFocus); - QSettings settings; - bool testMode = settings.value("chipTestMode", "").toString() == "Intan Chip Test Mode"; - settings.beginGroup("XDAQ"); - if (defaultSampleRateCheckBox->isChecked()) { - sampleRate = (AmplifierSampleRate) settings.value("defaultSampleRate", 14).toInt(); - stimStepSize = (StimStepSize) settings.value("defaultStimStepSize", 6).toInt(); - } else { - StartupDialog *startupDialog = new StartupDialog(controllerType, &sampleRate, &stimStepSize, &rememberSettings, true, testMode, this); - startupDialog->exec(); - - if (rememberSettings) { - settings.setValue("useDefaultSettings", true); - settings.setValue("defaultSampleRate", (int) sampleRate); - settings.setValue("defaultStimStepSize", (int) stimStepSize); - } else { - settings.setValue("useDefaultSettings", false); + std::vector> board_launch_properties; + + auto insert_board = [boardTable, launch_panel, &board_launch_properties](auto &&board) { + auto [app_icon, device_widget, launch_button_widget, launch_properties] = board; + board_launch_properties.push_back(launch_properties); + auto row = boardTable->rowCount(); + boardTable->insertRow(row); + boardTable->setItem(row, 0, app_icon); + boardTable->setCellWidget(row, 1, device_widget); + launch_button_widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + launch_panel->addWidget(launch_button_widget); + }; + + // Insert playback board. + insert_board(get_playback_board( + this, + [this](AbstractRHXController *controller, DataFileReader *data_file) { + QSettings settings; + settings.beginGroup("XDAQ"); + auto use_opencl = settings.value("useOpenCL", true).toBool(); + settings.endGroup(); + this->emit launch(controller, data_file->stimStepSize(), data_file, use_opencl, false); } + )); + + for (const auto &info : xdaq_infos) { + insert_board(get_xdaq_board( + this, + [this](AbstractRHXController *controller, StimStepSize step_size) { + QSettings settings; + settings.beginGroup("XDAQ"); + auto use_opencl = settings.value("useOpenCL", true).toBool(); + settings.endGroup(); + this->emit launch(controller, step_size, nullptr, use_opencl, false); + }, + info + )); } - settings.endGroup(); - splash->show(); - splash->showMessage(splashMessage, splashMessageAlign, splashMessageColor); - - startSoftware(controllerType, sampleRate, stimStepSize, controllersInfo.at(row)->numSPIPorts, - controllersInfo.at(row)->expConnected, controllersInfo.at(row)->serialNumber, LiveMode, false, nullptr, info); - - splash->finish(controlWindow); - this->accept(); -} + insert_board(get_demo_board( + this, + [this](AbstractRHXController *controller, StimStepSize step_size) { + QSettings settings; + settings.beginGroup("XDAQ"); + auto use_opencl = settings.value("useOpenCL", true).toBool(); + settings.endGroup(); + this->emit launch(controller, step_size, nullptr, use_opencl, false); + } + )); -// Allow user to load an Intan data file for playback. -void BoardSelectDialog::playbackDataFile() -{ - QSettings settings; - QString defaultDirectory = settings.value("playbackDirectory", ".").toString(); - QString playbackFileName; - playbackFileName = QFileDialog::getOpenFileName(this, tr("Select Intan Data File"), defaultDirectory, tr("Intan Data Files (*.rhd *.rhs)")); - if (playbackFileName.isEmpty()) { - exit(EXIT_FAILURE); - } + // Make table visible in full (for up to 5 rows... then allow a scroll bar to be used). + boardTable->setIconSize(QSize(283, 100)); + boardTable->resizeColumnsToContents(); + boardTable->resizeRowsToContents(); + boardTable->setMinimumSize(calculateTableSize(boardTable)); + boardTable->setSelectionBehavior(QAbstractItemView::SelectRows); + boardTable->setSelectionMode(QAbstractItemView::SingleSelection); - bool canReadFile = false; - QString report; - dataFileReader = new DataFileReader(playbackFileName, canReadFile, report, playbackPorts); - if (!canReadFile) { - ScrollableMessageBoxDialog msgBox(this, "Unable to Load Data File", report); - msgBox.exec(); - delete dataFileReader; - dataFileReader = nullptr; - exit(EXIT_FAILURE); - } else if (!report.isEmpty()) { - ScrollableMessageBoxDialog msgBox(this, "Data File Loaded", report); - msgBox.exec(); - QFileInfo fileInfo(playbackFileName); - settings.setValue("playbackDirectory", fileInfo.absolutePath()); - } + launch_panel->addWidget(new QLabel(tr("Select a board to launch"))); + launch_panel->setCurrentIndex(launch_panel->count() - 1); + connect(boardTable, &QTableWidget::currentCellChanged, [boardTable, launch_panel, this]() { + auto current_row = boardTable->currentRow(); + if (current_row == -1) return; + launch_panel->currentWidget()->hide(); + launch_panel->setCurrentIndex(current_row); + launch_panel->resize(launch_panel->currentWidget()->sizeHint()); + launch_panel->currentWidget()->show(); + this->resize(this->sizeHint()); + }); - splash->show(); - splash->showMessage(splashMessage, splashMessageAlign, splashMessageColor); - startSoftware(dataFileReader->controllerType(), dataFileReader->sampleRate(), dataFileReader->stimStepSize(), - dataFileReader->numSPIPorts(), dataFileReader->expanderConnected(), "N/A", PlaybackMode, false, dataFileReader, nullptr); + // Allow the user to open 'Advanced' dialog to allow opting out of OpenCL + auto advancedButton = new QPushButton(tr("Advanced"), this); + advancedButton->setFixedWidth(advancedButton->sizeHint().width() + 10); + connect(advancedButton, &QPushButton::clicked, this, [&]() { + QSettings settings; + settings.beginGroup("XDAQ"); + bool use_opencl = true; + std::uint8_t playback_ports = 255; + AdvancedStartupDialog advancedStartupDialog(use_opencl, playback_ports, false, this); + advancedStartupDialog.exec(); + settings.setValue("useOpenCL", use_opencl); + settings.setValue("playbackPorts", playback_ports); + settings.endGroup(); + }); + + auto mainLayout = new QVBoxLayout; + auto boardsLayout = new QHBoxLayout; + boardsLayout->addWidget(boardTable); + boardsLayout->addWidget(launch_panel); + mainLayout->addLayout(boardsLayout); + mainLayout->addWidget(create_default_settings_file_checkbox(nullptr)); + mainLayout->addWidget(create_default_sample_rate_checkbox(nullptr)); + mainLayout->addWidget(advancedButton); - splash->finish(controlWindow); - this->accept(); -} + setWindowTitle("Select XDAQ"); + setLayout(mainLayout); -void BoardSelectDialog::advanced() -{ - AdvancedStartupDialog advancedStartupDialog(useOpenCL, playbackPorts, false, this); - advancedStartupDialog.exec(); -} + // auto splash = new QSplashScreen(QPixmap(":images/RHX_splash.png")); + // auto splashMessage = "Copyright " + CopyrightSymbol + " " + ApplicationCopyrightYear + + // " Intan Technologies. RHX version " + SoftwareVersion + + // ". Opening XDAQ ..."; + // auto splashMessageAlign = Qt::AlignCenter | Qt::AlignBottom; + // auto splashMessageColor = Qt::white; +} \ No newline at end of file diff --git a/GUI/Dialogs/boardselectdialog.h b/GUI/Dialogs/boardselectdialog.h index c9e99a2..8b13d8c 100644 --- a/GUI/Dialogs/boardselectdialog.h +++ b/GUI/Dialogs/boardselectdialog.h @@ -28,124 +28,26 @@ // //------------------------------------------------------------------------------ -#ifndef BOARDSELECTDIALOG_H -#define BOARDSELECTDIALOG_H +#pragma once #include +#include -#include "demodialog.h" -#include "startupdialog.h" -#include "rhxcontroller.h" -#include "syntheticrhxcontroller.h" -#include "playbackrhxcontroller.h" -#include "rhxglobals.h" -#include "controlwindow.h" -#include "controllerinterface.h" -#include "systemstate.h" -#include "commandparser.h" - -// const QString RHDBoardString = "RHD USB Interface Board"; -// const QString RHD512chString = "RHD 512ch Recording Controller"; -// const QString RHD1024chString = "RHD 1024ch Recording Controller"; -// const QString RHS128chString = "RHS 128ch Stim/Recording Controller"; -// const QString CLAMP2chString = "2ch CLAMP Controller"; -// const QString CLAMP8chString = "8ch CLAMP Controller"; -// const QString UnknownUSB2String = "Unknown USB2 Device"; -// const QString UnknownUSB3String = "Unknown USB3 Device"; -// const QString UnknownString = "Unknown Device"; -// const QString RHS128ch_7310String = "RHS 128ch Stim/Recording Controller (7310)"; -// const QString RHD512ch_7310String = "RHD 512ch Recording Controller (7310)"; -// const QString RHD1024ch_7310String = "RHD 1024ch Recording Controller (7310)"; - -enum class XDAQModel{ - Unknown = 0, - Core = 1, - One = 3 -}; - -struct ControllerInfo { - QString serialNumber; - QString xdaqSerial; - XDAQModel xdaqModel; - bool expConnected; - int numSPIPorts; - int maxRHDchannels; - int maxRHSchannels; - BoardMode boardMode; -}; - -class BoardIdentifier -{ -public: - BoardIdentifier(QWidget* parent_); - ~BoardIdentifier(); - - QVector getConnectedControllersInfo(); - -private: - void identifyController(ControllerInfo* controller, int index); - QString opalKellyModelName(int model) const; - bool uploadFpgaBitfileQMessageBox(const QString& filename); - - QVector controllers; - QWidget *parent; - - okCFrontPanel *dev; -}; - -class QPushButton; -class QTableWidget; +#include "../../Engine/API/Hardware/controller_info.h" +#include "abstractrhxcontroller.h" +#include "datafilereader.h" class BoardSelectDialog : public QDialog { Q_OBJECT public: - BoardSelectDialog(QWidget *parent = nullptr); - ~BoardSelectDialog(); - - static bool validControllersPresent(QVector cInfo); - -private slots: - void openSelectedBoard(); - void newRowSelected(int row); - void startBoard(int row); - void playbackDataFile(); - void advanced(); - -private: - void populateTable(); - QSize calculateTableSize(); - - void showDemoMessageBox(); - void startSoftware(ControllerType controllerType, AmplifierSampleRate sampleRate, StimStepSize stimStepSize, - int numSPIPorts, bool expanderConnected, const QString& boardSerialNumber, AcquisitionMode mode, - bool is7310, DataFileReader* dataFileReader, const ControllerInfo* info); - - QTableWidget *boardTable; - QPushButton *openButton; - QPushButton *playbackButton; - QPushButton *advancedButton; - - QCheckBox *defaultSampleRateCheckBox; - QCheckBox *defaultSettingsFileCheckBox; - - QSplashScreen* splash; - QString splashMessage; - int splashMessageAlign; - QColor splashMessageColor; - - BoardIdentifier *boardIdentifier; - QVector controllersInfo; - DataFileReader *dataFileReader; - - AbstractRHXController *rhxController; - SystemState *state; - ControllerInterface *controllerInterface; - CommandParser *parser; - ControlWindow *controlWindow; - - bool useOpenCL; - uint8_t playbackPorts; + BoardSelectDialog(QWidget *parent, const std::vector &xdaq_infos); + +signals: + void launch( + AbstractRHXController *controller, // Physical or simulated controller + StimStepSize step_size, // Pass to Controller for AUX commands + DataFileReader *data_file, // For the seek events + bool OpenCL, bool test_mode + ); }; - -#endif // BOARDSELECTDIALOG_H diff --git a/conanfile.txt b/conanfile.txt new file mode 100644 index 0000000..02b117a --- /dev/null +++ b/conanfile.txt @@ -0,0 +1,10 @@ +[requires] +boost/1.81.0 +fmt/[>=10.0.0 <=10.2.0] +json-schema-validator/2.2.0 +spdlog/1.12.0 + +[generators] +CMakeDeps +CMakeToolchain +VirtualRunEnv \ No newline at end of file diff --git a/main.cpp b/main.cpp index ff386a2..3834690 100644 --- a/main.cpp +++ b/main.cpp @@ -27,18 +27,337 @@ // See for documentation and product information. // //------------------------------------------------------------------------------ +#include +#include #include +#include +#include +#include +#include +#include + +#include "Engine/API/Hardware/controller_info.h" +#include "abstractrhxcontroller.h" #include "boardselectdialog.h" +#include "commandparser.h" +#include "controllerinterface.h" +#include "controlwindow.h" +#include "datafilereader.h" +#include "playbackrhxcontroller.h" +#include "rhxcontroller.h" +#include "rhxglobals.h" +#include "syntheticrhxcontroller.h" +#include "systemstate.h" + +using json = nlohmann::json; +namespace fs = std::filesystem; + +auto get_plugins() +{ +#ifdef __WIN32__ + std::vector search_path = {R"(.\plugins)"}; + constexpr auto extention = ".dll"; +#elif __APPLE__ + std::vector search_path = {"/usr/local/lib/xdaq/plugins", "./plugins"}; + constexpr auto extention = ".dylib"; +#elif __linux__ + std::vector search_path = {"/usr/local/lib/xdaq/plugins", "./plugins"}; + constexpr auto extention = ".so"; +#endif + std::unordered_set paths; + for (const auto &p : search_path) { + if (!fs::exists(p) || !fs::is_directory(p)) continue; + for (const auto &entry : fs::directory_iterator(p)) { + if (!fs::is_regular_file(entry) && !fs::is_symlink(entry)) continue; + if (entry.path().extension() != extention) continue; + paths.insert(fs::canonical(entry.path())); + } + } + std::vector plugins; + for (const auto &path : paths) { + auto plugin = xdaq::load_device_plugin(path); + if (plugin.has_value()) plugins.emplace_back(std::move(plugin.value())); + } + return std::move(plugins); +} + +struct RHXAPP { + // rhxController -> state (Q) -> controllerInterface (Q) -> parser (Q) -> controlWindow (Q: no + // parent) + std::unique_ptr rhxController; + std::unique_ptr state; // Keep the order of destruction + // std::unique_ptr controlWindow; + ControlWindow *controlWindow; +}; + +auto startSoftware( + ControllerType controllerType, StimStepSize stimStepSize, DataFileReader *dataFileReader, + AbstractRHXController *rhxController, QString defaultSettingsFile, bool useOpenCL, bool testMode +) +{ + bool is7310 = false; + RHXAPP app{.rhxController = std::unique_ptr(rhxController)}; + XDAQInfo xdaqinfo; + + QSettings settings; + + app.state = std::make_unique( + app.rhxController.get(), + stimStepSize, + controllerType == ControllerStimRecord ? 4 : 8, + xdaqinfo.expander, + testMode, + dataFileReader, + xdaqinfo.model == XDAQModel::One, + (xdaqinfo.model == XDAQModel::Core ? 1 : 2) + ); + + // app.state->highDPIScaleFactor = + // main->devicePixelRatio(); // Use this to adjust graphics for high-DPI monitors. + app.state->availableScreenResolution = QGuiApplication::primaryScreen()->geometry(); + auto controllerInterface = new ControllerInterface( + app.state.get(), + app.rhxController.get(), + "", + useOpenCL, + dataFileReader, + app.state.get(), + is7310 + ); + app.state->setupGlobalSettingsLoadSave(controllerInterface); + auto parser = new CommandParser(app.state.get(), controllerInterface, controllerInterface); + app.controlWindow = + new ControlWindow(app.state.get(), parser, controllerInterface, app.rhxController.get()); + // auto controlWindow = app.controlWindow.get(); // Keep weak pointer for easy access + auto controlWindow = app.controlWindow; // Keep weak pointer for easy access + parser->controlWindow = controlWindow; + + QObject::connect( + controlWindow, + SIGNAL(sendExecuteCommand(QString)), + parser, + SLOT(executeCommandSlot(QString)) + ); + QObject::connect( + controlWindow, + SIGNAL(sendExecuteCommandWithParameter(QString, QString)), + parser, + SLOT(executeCommandWithParameterSlot(QString, QString)) + ); + QObject::connect( + controlWindow, SIGNAL(sendGetCommand(QString)), parser, SLOT(getCommandSlot(QString)) + ); + QObject::connect( + controlWindow, + SIGNAL(sendSetCommand(QString, QString)), + parser, + SLOT(setCommandSlot(QString, QString)) + ); + + QObject::connect( + parser, + SIGNAL(stimTriggerOn(QString)), + controllerInterface, + SLOT(manualStimTriggerOn(QString)) + ); + QObject::connect( + parser, + SIGNAL(stimTriggerOff(QString)), + controllerInterface, + SLOT(manualStimTriggerOff(QString)) + ); + QObject::connect( + parser, + SIGNAL(stimTriggerPulse(QString)), + controllerInterface, + SLOT(manualStimTriggerPulse(QString)) + ); + + QObject::connect(parser, SIGNAL(updateGUIFromState()), controlWindow, SLOT(updateFromState())); + QObject::connect( + parser, + SIGNAL(sendLiveNote(QString)), + controllerInterface->saveThread(), + SLOT(saveLiveNote(QString)) + ); + + QObject::connect( + controllerInterface, SIGNAL(TCPErrorMessage(QString)), parser, SLOT(TCPErrorSlot(QString)) + ); + + if (dataFileReader) { + QObject::connect( + controlWindow, + SIGNAL(setDataFileReaderSpeed(double)), + dataFileReader, + SLOT(setPlaybackSpeed(double)) + ); + QObject::connect( + controlWindow, SIGNAL(setDataFileReaderLive(bool)), dataFileReader, SLOT(setLive(bool)) + ); + QObject::connect(controlWindow, SIGNAL(jumpToEnd()), dataFileReader, SLOT(jumpToEnd())); + QObject::connect(controlWindow, SIGNAL(jumpToStart()), dataFileReader, SLOT(jumpToStart())); + QObject::connect( + controlWindow, + SIGNAL(jumpToPosition(QString)), + dataFileReader, + SLOT(jumpToPosition(QString)) + ); + QObject::connect( + controlWindow, SIGNAL(jumpRelative(double)), dataFileReader, SLOT(jumpRelative(double)) + ); + QObject::connect( + controlWindow, + SIGNAL(setStatusBarReadyPlayback()), + dataFileReader, + SLOT(setStatusBarReady()) + ); + QObject::connect( + dataFileReader, + SIGNAL(setStatusBar(QString)), + controlWindow, + SLOT(updateStatusBar(QString)) + ); + QObject::connect( + dataFileReader, + SIGNAL(setTimeLabel(QString)), + controlWindow, + SLOT(updateTimeLabel(QString)) + ); + QObject::connect( + dataFileReader, + SIGNAL(sendSetCommand(QString, QString)), + parser, + SLOT(setCommandSlot(QString, QString)) + ); + } + + QObject::connect( + controllerInterface, SIGNAL(haveStopped()), controlWindow, SLOT(stopAndReportAnyErrors()) + ); + QObject::connect( + controllerInterface, + SIGNAL(setTimeLabel(QString)), + controlWindow, + SLOT(updateTimeLabel(QString)) + ); + QObject::connect( + controllerInterface, + SIGNAL(setTopStatusLabel(QString)), + controlWindow, + SLOT(updateTopStatusLabel(QString)) + ); + QObject::connect( + controllerInterface, + SIGNAL(setHardwareFifoStatus(double)), + controlWindow, + SLOT(updateHardwareFifoStatus(double)) + ); + QObject::connect( + controllerInterface, + SIGNAL(cpuLoadPercent(double)), + controlWindow, + SLOT(updateMainCpuLoad(double)) + ); + + QObject::connect( + controllerInterface->saveThread(), + SIGNAL(setStatusBar(QString)), + controlWindow, + SLOT(updateStatusBar(QString)) + ); + QObject::connect( + controllerInterface->saveThread(), + SIGNAL(setTimeLabel(QString)), + controlWindow, + SLOT(updateTimeLabel(QString)) + ); + QObject::connect( + controllerInterface->saveThread(), + SIGNAL(sendSetCommand(QString, QString)), + parser, + SLOT(setCommandSlot(QString, QString)) + ); + QObject::connect( + controllerInterface->saveThread(), + SIGNAL(error(QString)), + controlWindow, + SLOT(queueErrorMessage(QString)) + ); + + controlWindow->show(); + if (!defaultSettingsFile.isEmpty()) { + if (controlWindow->loadSettingsFile(defaultSettingsFile)) { + emit controlWindow->setStatusBar("Loaded default settings file " + defaultSettingsFile); + } else { + emit controlWindow->setStatusBar( + "Error loading default settings file " + defaultSettingsFile + ); + } + } + return std::move(app); +} int main(int argc, char *argv[]) { + auto plugins = get_plugins(); + std::vector devices; + std::vector controller_infos; + + std::shared_ptr opened_device; + + for (auto &plugin : plugins) { + fmt::print("Plugin: {}\n", plugin.path); + auto device = plugin.list_devices(); + fmt::print("Device: {}\n", device); + auto device_json = json::parse(device); + for (const auto &device : device_json) { + auto dev = plugin.create(device.dump()); + auto info = read_xdaq_info(dev.get()); + info.plugin = plugin.path; + info.device_config = device.dump(); + info.get_device = [&opened_device, &plugin](std::string device) { + opened_device = plugin.create(device); + opened_device->set_register_sync(0, 1 << 24, 1 << 24); + return opened_device.get(); + }; + controller_infos.push_back(info); + } + } + QApplication app(argc, argv); + // Information used by QSettings to save basic settings across sessions. + QCoreApplication::setOrganizationName(OrganizationName); + QCoreApplication::setOrganizationDomain(OrganizationDomain); + QCoreApplication::setApplicationName(ApplicationName); + // Globally disable unused Context Help buttons from windows/dialogs + QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); #ifdef __APPLE__ app.setStyle(QStyleFactory::create("Fusion")); #endif - BoardSelectDialog boardSelectDialog; + BoardSelectDialog boardSelectDialog(nullptr, controller_infos); + // print size of boardSelectDialog + std::vector apps; + boardSelectDialog.show(); + QObject::connect( + &boardSelectDialog, + &BoardSelectDialog::launch, + [&apps, &boardSelectDialog]( + AbstractRHXController *controller, + StimStepSize step_size, + DataFileReader *data_file, + bool OpenCL, + bool test_mode + ) { + apps.push_back(std::move(startSoftware( + controller->getType(), step_size, data_file, controller, "", OpenCL, test_mode + ))); + boardSelectDialog.accept(); + } + ); + return app.exec(); }