From a6be70fb7b91238ff063ae8886941a4cc208fcd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiesner=20Andr=C3=A1s?= Date: Thu, 5 May 2022 22:19:52 +0200 Subject: [PATCH] - MultiStreamOscilloscope first implementation done - SlopeTrigger -> EdgeTrigger - working Python example included - libwfr.h introduced for easy including all libwfr headers - ChannelBuffer is now template - Semaphore moved --- CMakeLists.txt | 4 +- libwfr/CMakeLists.txt | 5 +- libwfr/src/ChannelBuffer.cpp | 238 +----------------- libwfr/src/ChannelBuffer.h | 286 ++++++++++++++++++++-- libwfr/src/MultiStreamOscilloscope.cpp | 170 +++++++++++-- libwfr/src/MultiStreamOscilloscope.h | 46 +++- libwfr/src/MultiStreamProcessor.cpp | 4 + libwfr/src/MultiStreamProcessor.h | 1 + libwfr/src/MultiStreamReceiver.cpp | 51 +++- libwfr/src/MultiStreamReceiver.h | 3 +- libwfr/src/MultiStreamToFile.cpp | 2 +- libwfr/src/SampleWriter.cpp | 4 +- libwfr/src/ServerBeacon.cpp | 4 +- libwfr/src/ServerBeacon.h | 2 +- libwfr/src/Trigger.cpp | 17 +- libwfr/src/Trigger.h | 11 +- libwfr/src/audio_types.h | 7 +- libwfr/src/libwfr.h | 22 ++ {utils => libwfr/src/utils}/Semaphore.cpp | 0 {utils => libwfr/src/utils}/Semaphore.h | 0 main.cpp | 25 +- python/src/wfsmodule.cpp | 49 +++- src/ALSADevice.h | 2 +- 23 files changed, 632 insertions(+), 321 deletions(-) create mode 100644 libwfr/src/libwfr.h rename {utils => libwfr/src/utils}/Semaphore.cpp (100%) rename {utils => libwfr/src/utils}/Semaphore.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 111347d..2649807 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,8 @@ add_executable(${APP_NAME} src/ALSADevice.h src/MemoryPool.cpp src/MemoryPool.h - utils/Semaphore.h - utils/Semaphore.cpp) + libwfr/src/utils/Semaphore.h + libwfr/src/utils/Semaphore.cpp) find_package(Threads REQUIRED) if (THREADS_FOUND) diff --git a/libwfr/CMakeLists.txt b/libwfr/CMakeLists.txt index d784b8c..5f3558c 100644 --- a/libwfr/CMakeLists.txt +++ b/libwfr/CMakeLists.txt @@ -30,7 +30,10 @@ set(SOURCES src/MultiStreamOscilloscope.cpp src/MultiStreamOscilloscope.h src/Trigger.cpp - src/Trigger.h) + src/Trigger.h + src/libwfr.h + src/utils/Semaphore.cpp + src/utils/Semaphore.h) ### diff --git a/libwfr/src/ChannelBuffer.cpp b/libwfr/src/ChannelBuffer.cpp index 62a19f0..8dfa80a 100644 --- a/libwfr/src/ChannelBuffer.cpp +++ b/libwfr/src/ChannelBuffer.cpp @@ -1,239 +1,3 @@ // // Created by epagris on 2022.05.03.. -// - -#include -#include "ChannelBuffer.h" -#include "Logger.h" - -ChannelBuffer::ChannelBuffer(size_t blockSize, size_t blockCount, size_t elementSize) { - // check if blockSize is divisible by 4 - if ((blockSize % 4) != 0) { - throw std::runtime_error(std::string("Block size must be an integral multiple of 4! ") + __FUNCTION__); - } - - // fill-in parameters - mBlockSize = blockSize; - mBlockCount = blockCount; - mFullMemSize = blockSize * blockCount; - mElementSize = elementSize; - mElementsPerBlock = blockSize / elementSize; - - // allocate memory - mpMem = std::shared_ptr(new uint8_t[mFullMemSize]); // exception is thrown if this fails... - - // clear and fill-in memory blocks - clear(); - - // no reader is waiting - mReaderWaiting = false; -} - -void ChannelBuffer::store(const void *pSrc) { - mStoreFetch_mtx.lock(); // MUTEX!!! - - BlockDescriptor bd; - - if (mFreeBlocks.empty()) { // if there're no more free blocks to allocate, then drop the eldest one and shift the queue - // release block - bd = mOccupiedBlocks.front(); - mOccupiedBlocks.pop_front(); - - // log buffer overrun - //Logger::logLine("ChannelBuffer overrun!"); - - } else { // if there's at least a single free block - // acquire the free block - bd = mFreeBlocks.front(); - - // remove from the free queue - mFreeBlocks.pop_front(); - - // put into the queue of occupied blocks - //mOccupiedBlocks.push(bd); - } - - // store tag - mLastTag += mElementsPerBlock; - bd.tag = mLastTag; - - // push back onto the queue ("move to the end of the queue") - mOccupiedBlocks.push_back(bd); - - // copy content - void *pDest = bd.p; - memcpy(pDest, pSrc, mBlockSize); - - // handle waiting readers - if (mReaderWaiting) { - mReaderWaiting = false; - mStoreFetch_mtx.unlock(); - mReadSem.post(); - } else { - mStoreFetch_mtx.unlock(); - } -} - -uint64_t ChannelBuffer::fetch(void *pDst, ssize_t size) { - mStoreFetch_mtx.lock(); // MUTEX!!! - - // if there're no data to copy, then block - if (mOccupiedBlocks.empty()) { - mReaderWaiting = true; // signal that reader is waiting - mStoreFetch_mtx.unlock(); // release lock - mReadSem.wait(); // wait on data to become available - mStoreFetch_mtx.lock(); // re-lock mutex - } - - BlockDescriptor bd; - - // if there're data to copy - if (!mOccupiedBlocks.empty()) { - // get block - bd = mOccupiedBlocks.front(); - - void *pSrc = bd.p; - - // copy content to given memory area - if (size == 0) { - memcpy(pDst, pSrc, mBlockSize); - } else if (size > 0) { // copy from the beginning of the block - memcpy(pDst, pSrc, std::min((size_t) size, mBlockSize)); - } else if (size < 0) { // copy from the end of the block - size_t copySize = std::min((size_t) (labs(size)), mBlockSize); // determine copy size - uint8_t *pSrcStart = (uint8_t *) pSrc + mBlockSize - copySize; // determine beginning of area to copy - memcpy(pDst, pSrcStart, copySize); // copy - } - - // remove from the queue of occupied blocks - mOccupiedBlocks.pop_front(); - - // add to the queue of free blocks - mFreeBlocks.push_back(bd); - } else { - throw std::runtime_error("Error, this mustn't be hit!"); - } - - mStoreFetch_mtx.unlock(); // release semaphore - - return bd.tag; -} - -void ChannelBuffer::clear() { - std::unique_lock lock(mStoreFetch_mtx); // MUTEX! - - // fill-in free blocks - mOccupiedBlocks.clear(); - mFreeBlocks.clear(); - for (size_t i = 0; i < mBlockCount; i++) { - BlockDescriptor bd = {.p = mpMem.get() + i * mBlockSize, .tag = -1}; - mFreeBlocks.push_back(bd); - } -} - -std::shared_ptr ChannelBuffer::fullBuffer() const { - return mpMem; -} - -int64_t ChannelBuffer::getEldestTag() { - std::unique_lock lock(mStoreFetch_mtx); - - if (!mOccupiedBlocks.empty()) { - return mOccupiedBlocks.front().tag; - } else { - return -1; - } -} - -size_t ChannelBuffer::copyBetweenTags(void *pDst, int64_t start, int64_t end) { - std::unique_lock lock(mStoreFetch_mtx); - - if (mOccupiedBlocks.empty() || (end < start)) { - return 0; - } - - uint8_t * pDstByte = (uint8_t *) pDst; - - // get eldest and youngest tag - int64_t eldestTag = mOccupiedBlocks.front().tag; - int64_t youngestTag = mOccupiedBlocks.back().tag; - - // clip tags falling out of valid range - start = std::max(start, eldestTag); - end = std::min(end, youngestTag); - - // iterators - std::list::iterator startIter, endIter; - bool startIterFound = false, endIterFound = false; - - // find starting block - for (auto iter = mOccupiedBlocks.begin(); iter != mOccupiedBlocks.end() && (!startIterFound && !endIterFound); iter++) { - // select start iter - if ((iter->tag <= start) && ((iter->tag + mElementsPerBlock - 1) >= start)) { - startIter = iter; - startIterFound = true; - } - if ((iter->tag <= end) && ((iter->tag + mElementsPerBlock - 1) >= end)) { - endIter = iter; - endIterFound = true; - } - } - - uint8_t *pStart; - size_t sizeCopied = 0; - - // IF range resides in a single block - if (startIter == endIter) { - pStart = startIter->p + (start - startIter->tag) * mElementSize; - sizeCopied = end - start + 1; - memcpy(pDst, pStart, sizeCopied); - } else { // IF range spans across multiple blocks - auto iterAfterEnd = endIter; - iterAfterEnd++; - - size_t copySize = 0; - for (auto iter = startIter; iter != iterAfterEnd; iter++) { - if (iter == startIter) { // START - pStart = iter->p + (start - iter->tag) * mElementSize; - copySize = mBlockSize - (start - iter->tag) + 1; - memcpy(pDstByte + sizeCopied, pStart, copySize); - sizeCopied += copySize; - } else if (iter == endIter) { // END - pStart = iter->p; - copySize = (end - iter->tag) + 1; - memcpy(pDstByte + sizeCopied, pStart, copySize); - sizeCopied += copySize; - } else { // INBETWEEN - pStart = iter->p; - copySize = mBlockSize; - memcpy(pDstByte + sizeCopied, pStart, copySize); - sizeCopied += copySize; - } - } - } - - return sizeCopied; -} - -size_t ChannelBuffer::getFreeBlockCnt() { - std::unique_lock lock(mStoreFetch_mtx); - return mFreeBlocks.size(); -} - -int64_t ChannelBuffer::copyBlock(size_t n, void * pDst) { - std::unique_lock lock(mStoreFetch_mtx); - - // block not found - if ((n + 1) > mOccupiedBlocks.size()) { - return -1; - } - - // block found - auto iter = mOccupiedBlocks.begin(); - std::advance(iter, n); - - // copy block contents - memcpy(pDst, iter->p, mBlockSize); - - return iter->tag; -} +// \ No newline at end of file diff --git a/libwfr/src/ChannelBuffer.h b/libwfr/src/ChannelBuffer.h index 8a5ffa5..30ac926 100644 --- a/libwfr/src/ChannelBuffer.h +++ b/libwfr/src/ChannelBuffer.h @@ -12,41 +12,295 @@ #include #include #include -#include +#include + +#include "utils/Semaphore.h" +#include "Logger.h" /* * Finite-length, automatically dropping channel buffer designed for exactly one reader */ +template class ChannelBuffer { public: struct BlockDescriptor { - uint8_t * p; + T *p; int64_t tag; }; private: - size_t mBlockSize; // size of a block + size_t mElementsPerBlock; // size of a block size_t mBlockCount; // number of blocks - size_t mFullMemSize; // allocate memory block size derived from block size and block count - size_t mElementSize; // size of a single element - size_t mElementsPerBlock; // number of elements per block + size_t mFullElementCnt; // allocate memory block size derived from block size and block count + //size_t mElementSize; // size of a single element + //size_t mElementsPerBlock; // number of elements per block uint64_t mLastTag; // last assigned tag std::list mOccupiedBlocks; // occupied blocks std::list mFreeBlocks; // free blocks - std::shared_ptr mpMem; // memory holding buffers (free and occupied also) + std::shared_ptr mpMem; // memory holding buffers (free and occupied also) std::mutex mStoreFetch_mtx; // mutex for concurrent fetching and storing bool mReaderWaiting; // indicates, that a reader is waiting for new data Semaphore mReadSem; // read semaphore for blocking reader public: - ChannelBuffer(size_t blockSize, size_t blockCount, size_t elementSize); // constr. - void store(const void *pSrc); // store into a new data block by copying from pSrc - uint64_t fetch(void * pDst, ssize_t size = 0); // fetch eldest data block and copy to pDst - std::shared_ptr fullBuffer() const; // get full buffer - void clear(); // clear block occupancies - int64_t getEldestTag(); // get the tag residing the longest in the buffer - size_t copyBetweenTags(void *pDst, int64_t start, int64_t end); // copy data to external buffer between tags (return: number of bytes copied) - size_t getFreeBlockCnt(); // get number of free blocks - int64_t copyBlock(size_t n, void * pDst); // get block (nth eldest) + // constr. + ChannelBuffer(size_t elementsPerBlock, size_t blockCount) { + // fill-in parameters + mElementsPerBlock = elementsPerBlock; + mBlockCount = blockCount; + mFullElementCnt = elementsPerBlock * blockCount; + + // allocate memory + mpMem = std::shared_ptr(new T[mFullElementCnt]); // exception is thrown if this fails... + + // clear and fill-in memory block queues + clear(); + + // no reader is waiting + mReaderWaiting = false; + + // clear last tag + mLastTag = 0; + } + + // store into a new data block by copying from pSrc + void store(const T *pSrc) { + mStoreFetch_mtx.lock(); // MUTEX!!! + + BlockDescriptor bd; + + if (mFreeBlocks.empty()) { // if there're no more free blocks to allocate, then drop the eldest one and shift the queue + // release block + bd = mOccupiedBlocks.front(); + mOccupiedBlocks.pop_front(); + + // log buffer overrun + //Logger::logLine("ChannelBuffer overrun!"); + + } else { // if there's at least a single free block + // acquire the free block + bd = mFreeBlocks.front(); + + // remove from the free queue + mFreeBlocks.pop_front(); + + // put into the queue of occupied blocks + //mOccupiedBlocks.push(bd); + } + + // store tag + mLastTag += mElementsPerBlock; + bd.tag = mLastTag; + + // push back onto the queue ("move to the end of the queue") + mOccupiedBlocks.push_back(bd); + + // copy content + memcpy(bd.p, pSrc, mElementsPerBlock * sizeof(T)); + + // handle waiting readers + if (mReaderWaiting) { + mReaderWaiting = false; + mStoreFetch_mtx.unlock(); + mReadSem.post(); + } else { + mStoreFetch_mtx.unlock(); + } + } + + // fetch eldest data block and copy to pDst + int64_t fetch(T *pDst, ssize_t n = 0) { + mStoreFetch_mtx.lock(); // MUTEX!!! + + // if there're no data to copy, then block + if (mOccupiedBlocks.empty()) { + mReaderWaiting = true; // signal that reader is waiting + mStoreFetch_mtx.unlock(); // release lock + mReadSem.wait(); // wait on data to become available + mStoreFetch_mtx.lock(); // re-lock mutex + } + + BlockDescriptor bd; + + // if there're data to copy + if (!mOccupiedBlocks.empty()) { + // get block + bd = mOccupiedBlocks.front(); + + T *pSrc = bd.p; + + // copy content to given memory area + if (n == 0) { + memcpy(pDst, pSrc, mElementsPerBlock * sizeof(T)); + } else if (n > 0) { // copy from the beginning of the block + memcpy(pDst, pSrc, std::min((size_t) n, mElementsPerBlock * sizeof(T))); + } else if (n < 0) { // copy from the end of the block + size_t copyN = std::min((size_t) (labs(n)), mElementsPerBlock) * sizeof(T); // determine copy n + T *pSrcStart = pSrc + mElementsPerBlock - copyN; // determine beginning of area to copy + memcpy(pDst, pSrcStart, copyN * sizeof(T)); // copy + } + + // remove from the queue of occupied blocks + mOccupiedBlocks.pop_front(); + + // add to the queue of free blocks + mFreeBlocks.push_back(bd); + } else { + throw std::runtime_error("Error, this mustn't be hit!"); + } + + mStoreFetch_mtx.unlock(); // release semaphore + + return bd.tag; + } + + // get full buffer + std::shared_ptr fullBuffer() const { + return mpMem; + } + + // clear block occupancies + void clear() { + std::lock_guard lock(mStoreFetch_mtx); // MUTEX! + + // fill-in free blocks + mOccupiedBlocks.clear(); + mFreeBlocks.clear(); + for (size_t i = 0; i < mBlockCount; i++) { + BlockDescriptor bd = {.p = mpMem.get() + i * mElementsPerBlock, .tag = -1}; + mFreeBlocks.push_back(bd); + } + } + + // get the tag residing the longest in the buffer + int64_t getEldestTag() { + std::lock_guard lock(mStoreFetch_mtx); + + if (!mOccupiedBlocks.empty()) { + return mOccupiedBlocks.front().tag; + } else { + return -1; + } + } + + // copy data to external buffer between tags (return: number of ELEMENTS copied) + size_t copyBetweenTags(T *pDst, int64_t start, int64_t end) { + std::lock_guard lock(mStoreFetch_mtx); + + if (mOccupiedBlocks.empty() || (end < start)) { + return 0; + } + + // get eldest and youngest tag + int64_t eldestTag = mOccupiedBlocks.front().tag; + int64_t youngestTag = mOccupiedBlocks.back().tag; + + // clip tags falling out of valid range + start = std::max(start, eldestTag); + end = std::min(end, youngestTag); + + // iterators + typename std::list::iterator startIter, endIter; + bool startIterFound = false, endIterFound = false; + + // find starting block + for (auto iter = mOccupiedBlocks.begin(); iter != mOccupiedBlocks.end() && !(startIterFound && endIterFound); iter++) { + // select start iter + if ((iter->tag <= start) && ((iter->tag + mElementsPerBlock - 1) >= start)) { + startIter = iter; + startIterFound = true; + } + if ((iter->tag <= end) && ((iter->tag + mElementsPerBlock - 1) >= end)) { + endIter = iter; + endIterFound = true; + } + } + + T *pStart; + size_t elementsCopied = 0; + + // IF range resides in a single block + if (startIter == endIter) { + pStart = startIter->p + (start - startIter->tag); + elementsCopied = end - start; + memcpy(pDst, pStart, elementsCopied * sizeof(T)); + } else { // IF range spans across multiple blocks + auto iterAfterEnd = endIter; + iterAfterEnd++; + + size_t copyN = 0; + for (auto iter = startIter; iter != iterAfterEnd; iter++) { + if (iter == startIter) { // START + pStart = iter->p + (start - iter->tag); + copyN = mElementsPerBlock - (start - iter->tag); + memcpy(pDst + elementsCopied, pStart, copyN * sizeof(T)); + elementsCopied += copyN; + } else if (iter == endIter) { // END + pStart = iter->p; + copyN = (end - iter->tag); + memcpy(pDst + elementsCopied, pStart, copyN * sizeof(T)); + elementsCopied += copyN; + } else { // INBETWEEN + pStart = iter->p; + copyN = mElementsPerBlock; + memcpy(pDst + elementsCopied, pStart, copyN * sizeof(T)); + elementsCopied += copyN; + } + } + } + + return elementsCopied; + } + + // get number of free blocks + size_t getFreeBlockCnt() { + std::lock_guard lock(mStoreFetch_mtx); + return mFreeBlocks.size(); + } + + // get block (nth eldest) + int64_t copyBlock(size_t n, T *pDst) { + std::lock_guard lock(mStoreFetch_mtx); + + // block not found + if ((n + 1) > mOccupiedBlocks.size()) { + return -1; + } + + // block found + auto iter = mOccupiedBlocks.begin(); + std::advance(iter, n); + + // copy block contents + memcpy(pDst, iter->p, mElementsPerBlock * sizeof(T)); + + return iter->tag; + } + + // get sample with given tag + T getElementByTag(int64_t tag) { + std::lock_guard lock(mStoreFetch_mtx); + + for (auto iter = mOccupiedBlocks.begin(); iter != mOccupiedBlocks.end(); iter++) { + if ((iter->tag <= tag) && ((iter->tag + mElementsPerBlock) > tag)) { + return *(iter->p + (tag - iter->tag)); + } + } + + // log invalid tag + Logger::logLine("INVALID tag (" + std::to_string(tag) + "), must be between " + + std::to_string(mOccupiedBlocks.front().tag) + " and " + + std::to_string(mOccupiedBlocks.back().tag + mElementsPerBlock - 1) + "!"); + + T invalid; + memset(&invalid, 0, sizeof(T)); + return invalid; + } + + // get eldest sample + T getEldestSample() { + std::lock_guard lock(mStoreFetch_mtx); + return *(mOccupiedBlocks.front().p); + } }; diff --git a/libwfr/src/MultiStreamOscilloscope.cpp b/libwfr/src/MultiStreamOscilloscope.cpp index 429b5ff..175a92c 100644 --- a/libwfr/src/MultiStreamOscilloscope.cpp +++ b/libwfr/src/MultiStreamOscilloscope.cpp @@ -3,12 +3,19 @@ // #include +#include #include "MultiStreamOscilloscope.h" MultiStreamOscilloscope::MultiStreamOscilloscope() { + trigger = std::make_shared(); // default, dummy trigger + mTriggerPosition_percent = TRIGGER_POS_PERCENT_DEF; mCapturePeriod_ns = DRAW_WINDOW_PERIOD_NS_DEF; verticalScale = VOLT_PER_BIN_DEF; - mTrigState = {false, false}; + mTrigState = {false, false, -1, false }; + + mFIFOBlockCnt = 0; + mCaptureLength = 0; + mPreTriggerSamples = 0; } void MultiStreamOscilloscope::setup(const std::vector &nodes, const AcquisitionFormat &acqFmt) { @@ -16,20 +23,36 @@ void MultiStreamOscilloscope::setup(const std::vector &nodes, const A // calculate buffer parameters mCaptureLength = ceil(mCapturePeriod_ns / 1E+09 * acqFmt.sampling_rate_Hz); // calculate draw window period in samples - mFIFOBlockCnt = 2 * floor(mCaptureLength / acqFmt.mch_samples_per_packet); // TODO: 2x: pretrigger... - mFIFOBlockSize = mAcqFmt.mch_samples_per_packet * sizeof(SamplePoint); + mFIFOBlockCnt = 4 * ceil(mCaptureLength / acqFmt.mch_samples_per_packet); // TODO magic... // setup channel buffers mpChBufs.resize(mChCnt); - for (auto chBuf: mpChBufs) { - chBuf.reset(new ChannelBuffer(mFIFOBlockSize, mFIFOBlockCnt, 0)); + for (auto& chBuf: mpChBufs) { + chBuf.reset(new ChannelBuffer(mAcqFmt.mch_samples_per_packet, mFIFOBlockCnt)); } + // setup fullness bits + mFIFOFull.resize(mChCnt); + clearFIFOFullnessBits(); + // setup sample assembly buffer mpAssemblyBuffer.reset(new SamplePoint[mAcqFmt.mch_samples_per_packet]); // setup trigger buffer mpTriggerBuffer.reset(new SamplePoint[mAcqFmt.mch_samples_per_packet]); + + // determine trigger block + double propTrigPos = mTriggerPosition_percent / 100.0; + mTriggerProbeBlock_idx = ceil((mFIFOBlockCnt - 2) * propTrigPos) + 1; // TODO it's too difficult to explain... + + // determine number of pretrigger samples + mPreTriggerSamples = ceil(mCaptureLength * propTrigPos); + + // setup capture buffers for every channel + mCaptureBuffers.resize(mChCnt); + for (size_t ch = 0; ch < mChCnt; ch++) { + mCaptureBuffers[ch].resize(mCaptureLength); + } } bool MultiStreamOscilloscope::input(size_t ch, const std::shared_ptr &pTime, const void *pData, size_t size) { @@ -59,42 +82,143 @@ bool MultiStreamOscilloscope::input(size_t ch, const std::shared_ptr sample *= verticalScale; // save sample point - mpAssemblyBuffer.get()[i] = {.t = pTime.get()[i].to_ns(), .x = sample}; + SamplePoint& sp = mpAssemblyBuffer.get()[i]; + sp.t = pTime.get()[i].to_ns(); + sp.x = sample; } // push new block of samples onto the FIFO mpChBufs[ch]->store(mpAssemblyBuffer.get()); + // store FIFO-fullness bit + mFIFOFull[ch] = (mpChBufs[ch]->getFreeBlockCnt() == 0); + // if a sufficient collection of samples are available (the FIFO is full) // and the trigger is armed, and the trigger channel is the curent one, // then probe samples to trigger - if (trigSettings.ch == ch && mTrigState.armed && mpChBufs[ch]->getFreeBlockCnt() == 0) { - int64_t tag = mpChBufs[ch]->copyBlock(2, mpTriggerBuffer.get()); // TODO magic constant!... - for (size_t i = 0; i < mAcqFmt.mch_samples_per_packet; i++) { - const SamplePoint& samplePoint = mpTriggerBuffer.get()[i]; // get sample - mTrigState.trigd = trigSettings.sample(samplePoint.x); // probe if causes a trigger + if (trigger->ch == ch && mTrigState.armed && mpChBufs[ch]->getFreeBlockCnt() == 0 && isEveryFIFOFull()) { + std::unique_lock lock(mCapture_mtx); + int64_t tag = mpChBufs[ch]->copyBlock(mTriggerProbeBlock_idx, mpTriggerBuffer.get()); // TODO magic constant!... - // triggered? - if (mTrigState.trigd) { - mTrigState.armed = false; // disable trigger system until arming - mTrigState.triggerSampleTag = tag + i; // store the sample the trigger fired on - - // copy captured data - size_t preTriggerSamples = mAcqFmt.mch_samples_per_packet; - int64_t captureStart = mTrigState.triggerSampleTag - preTriggerSamples; - int64_t captureEnd = captureStart + mCaptureLength; - mpChBufs[ch]->copyBetweenTags(mpTriggerBuffer.get(), captureStart, captureEnd); + size_t i = 0; + if (!mTrigState.trigd) { // if not triggered externally + while (i < mAcqFmt.mch_samples_per_packet) { + const SamplePoint &samplePoint = mpTriggerBuffer.get()[i]; // get sample + mTrigState.trigd = trigger->sample(samplePoint.x); // probe if causes a trigger + if (mTrigState.trigd) { // break if trigger is successful + break; + } + i++; // increase index } } + + // triggered? + if (mTrigState.trigd) { + mTrigState.armed = false; // disable trigger system until arming + mTrigState.triggerSampleTag = tag + i; // store the sample the trigger fired on + + // copy captured data + int64_t captureStart = mTrigState.triggerSampleTag - mPreTriggerSamples; + int64_t captureEnd = captureStart + mCaptureLength; + + // get time of trigger occurred + SamplePoint trigSP = mpChBufs[ch]->getElementByTag(mTrigState.triggerSampleTag); + int64_t t0 = trigSP.t; + + // get eldest sample's timestamp on the trigger channel + int64_t t_eldest_trigger = mpChBufs[ch]->getEldestSample().t; + + // TODO now full synchronicity is assumed + // copy samples from each channel + for (size_t k = 0; k < mChCnt; k++) { + auto& chBuf = *(mpChBufs[k]); // acquire channel buffer + + int64_t t_offset, offset_index = 0; + if (k != ch) { // we are NOT processing the trigger channel, since it's the reference + int64_t t_eldest = chBuf.getEldestSample().t; + t_offset = t_eldest - t_eldest_trigger; // k-th channel is t_offset time before the trigger channel + offset_index = round(t_offset * 1E-09 / mAcqFmt.sampling_rate_Hz); // determine offset in sample index + } + + chBuf.copyBetweenTags(mCaptureBuffers[k].data(), captureStart - offset_index, captureEnd - offset_index); + for (size_t l = 0; l < mCaptureLength; l++) { + mCaptureBuffers[k][l].t -= t0; // substract t0 from each sample point's time + } + } + + // clear each channel's FIFO (since we don't want the data to be displayed more than once) + for (size_t k = 0; k < mChCnt; k++) { + mpChBufs[k]->clear(); + } + + // notify thread waiting on data + mTrigState.samplesReady = true; + lock.unlock(); // release mutex + mCapture_cv.notify_all(); // notify + } + // automatically unlocks here } return true; } -void MultiStreamOscilloscope::triggerNow() { +void MultiStreamOscilloscope::clearFIFOFullnessBits() { + for (size_t i = 0; i < mChCnt; i++) { + mFIFOFull[i] = false; + } +} +bool MultiStreamOscilloscope::isEveryFIFOFull() const { + for (size_t i = 0; i < mChCnt; i++) { + if (!mFIFOFull[i]) { + return false; + } + } + return true; +} + +void MultiStreamOscilloscope::triggerNow() { + armTrigger(); + mTrigState.trigd = true; } void MultiStreamOscilloscope::armTrigger() { - + trigger->reset(); + mTrigState.trigd = false; + mTrigState.samplesReady = false; + mTrigState.armed = true; +} + +std::vector> MultiStreamOscilloscope::capture() { + std::unique_lock lock(mCapture_mtx); + mCapture_cv.wait(lock, [this] { return mTrigState.samplesReady; }); // wait for data to become available + + auto samples = mCaptureBuffers; // copy buffer + mTrigState.samplesReady = false; // invalidate samples + return samples; +} + +// ---------------------------------------- + +void MultiStreamOscilloscope::setScreenPeriod(size_t ns) { + mCapturePeriod_ns = ns; +} + +size_t MultiStreamOscilloscope::getScreenPeriod() const { + return mCapturePeriod_ns; +} + +void MultiStreamOscilloscope::setTriggerPosition(size_t percent) { + mTriggerPosition_percent = std::min(percent, (size_t) 100); +} + +size_t MultiStreamOscilloscope::getTriggerPosition() const { + return mTriggerPosition_percent; +} + +std::vector MultiStreamOscilloscope::getScreenTimeLimits() const { + int64_t lowerLimit, upperLimit; + lowerLimit = -ceil(mTriggerPosition_percent / 100.0 * mCapturePeriod_ns); + upperLimit = mCapturePeriod_ns + lowerLimit; + return std::vector({ lowerLimit, upperLimit }); } diff --git a/libwfr/src/MultiStreamOscilloscope.h b/libwfr/src/MultiStreamOscilloscope.h index 159bcfe..8d22f00 100644 --- a/libwfr/src/MultiStreamOscilloscope.h +++ b/libwfr/src/MultiStreamOscilloscope.h @@ -6,6 +6,7 @@ #define WFR_APP_MULTISTREAMOSCILLOSCOPE_H +#include #include "ICreatable.h" #include "MultiStreamProcessor.h" #include "ChannelBuffer.h" @@ -15,36 +16,65 @@ class MultiStreamOscilloscope : public MultiStreamProcessor, public ICreatable> mpChBufs; // channel buffers - size_t mCapturePeriod_ns; // drawing window period + +private: // data acquisition related things + std::vector>> mpChBufs; // channel buffers size_t mCaptureLength; // length of drawing sliding window in SAMPLES size_t mFIFOBlockCnt; // number of blocks the FIFO consists of - size_t mFIFOBlockSize; // FIFO block size in bytes + //size_t mFIFOBlockSize; // FIFO block size in bytes std::shared_ptr mpAssemblyBuffer; // buffer for assembling sample points std::shared_ptr mpTriggerBuffer; // buffer for probing against trigger conditions - std::shared_ptr mpCaptureBuffer; // buffer for last capture + std::vector> mCaptureBuffers; // buffer for last capture + std::mutex mCapture_mtx; // mutex for capture cv + std::condition_variable mCapture_cv; // condition variable on data capture + std::vector mFIFOFull; // stores FIFO fullness indicators, needed for proper triggering and capture +private: + void clearFIFOFullnessBits(); // clear fullness bits + bool isEveryFIFOFull() const; // are the FIFOs full? +public: // graphical displaying related things + size_t mTriggerPosition_percent; // trigger position on the screen + size_t mTriggerProbeBlock_idx; // index of block on which trigger will run + size_t mPreTriggerSamples; // samples displayed before the trigger point + size_t mCapturePeriod_ns; // (drawing window) screen period public: - TriggerSettings trigSettings; // trigger settings + std::shared_ptr trigger; // trigger settings double verticalScale; // vertical resolution private: struct { bool armed; // indicates wheter trigger is armed or not bool trigd; // indicates if trigger has fired int64_t triggerSampleTag; + bool samplesReady; // samples have been transferred to the output buffer } mTrigState; +public: + void setup(const std::vector &nodes, const AcquisitionFormat &acqFmt) override; + + bool input(size_t ch, const std::shared_ptr &pTime, const void *pData, size_t size) override; + public: MultiStreamOscilloscope(); + + void setScreenPeriod(size_t ns); // set screen (visible waveform window) time length + size_t getScreenPeriod() const; // get --- " ---- + void setTriggerPosition(size_t percent); // set trigger position + size_t getTriggerPosition() const; // get trigger position + std::vector getScreenTimeLimits() const; // get time limits of the screen's edges + void triggerNow(); // make the scope trigger regardless conditions void armTrigger(); // arm trigger for next acquisition + std::vector> capture(); // wait for captured data TODO to be renamed - void setup(const std::vector &nodes, const AcquisitionFormat &acqFmt) override; - bool input(size_t ch, const std::shared_ptr &pTime, const void *pData, size_t size) override; }; diff --git a/libwfr/src/MultiStreamProcessor.cpp b/libwfr/src/MultiStreamProcessor.cpp index 4b2e2cd..50d1b42 100644 --- a/libwfr/src/MultiStreamProcessor.cpp +++ b/libwfr/src/MultiStreamProcessor.cpp @@ -27,3 +27,7 @@ bool MultiStreamProcessor::input(size_t ch, const std::shared_ptr &pT return true; } + +size_t MultiStreamProcessor::getChannelCount() const { + return mChCnt; +} diff --git a/libwfr/src/MultiStreamProcessor.h b/libwfr/src/MultiStreamProcessor.h index 380d2dd..666fe09 100644 --- a/libwfr/src/MultiStreamProcessor.h +++ b/libwfr/src/MultiStreamProcessor.h @@ -22,6 +22,7 @@ protected: public: virtual void setup(const std::vector &nodes, const AcquisitionFormat &acqFmt); // setup function for adapting to data streams virtual bool input(size_t ch, const std::shared_ptr &pTime, const void *pData, size_t size); // input data for a channel + size_t getChannelCount() const; // get number of channels }; diff --git a/libwfr/src/MultiStreamReceiver.cpp b/libwfr/src/MultiStreamReceiver.cpp index 11eb841..d496ff3 100644 --- a/libwfr/src/MultiStreamReceiver.cpp +++ b/libwfr/src/MultiStreamReceiver.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "MultiStreamReceiver.h" #include "audio_types.h" @@ -34,7 +35,7 @@ MultiStreamReceiver::MultiStreamReceiver(const std::vector &nodes_s mSoc = -1; for (auto &node_addr: nodes_str) { - mClientNodes.push_back({ inet_addr(node_addr.c_str()) }); + mClientNodes.push_back({inet_addr(node_addr.c_str())}); } } @@ -102,7 +103,6 @@ void MultiStreamReceiver::stop() { mRunning = false; mRecvThread->join(); close(mSoc); - mpSampleWriters.clear(); } // -------------------------------------- @@ -129,10 +129,20 @@ void MultiStreamReceiver::fnRecv(MultiStreamReceiver *pMSR) { AudioPayloadHeader aph; // packet timestamps - Timestamp ts[2]; // current and previous timestamp + typedef Timestamp NodePacketTimestamps[2]; // current and previous timestamp + + // a pair for each node (since each node may have a unique timescale) + std::map allNodeTS; + for (auto node_ip: pMSR->mClientNodes) { // prepare clear timestamps + allNodeTS[node_ip][0] = Timestamp(0); + allNodeTS[node_ip][1] = Timestamp(0); + } + + //Timestamp ts[2]; // limit on time difference variation double timeDiffVariation_ns = 1E+09 / pMSR->mAcqFmt.sampling_rate_Hz * TS_VALIDITY_RANGE; + uint32_t lastIndex = 0; // allocate buffer for interpolated timestamps std::shared_ptr pTSBuf(new Timestamp[pMSR->mAcqFmt.mch_samples_per_packet]); @@ -171,8 +181,33 @@ void MultiStreamReceiver::fnRecv(MultiStreamReceiver *pMSR) { // get header memcpy(&aph, pRecvBuf.get(), sizeof(AudioPayloadHeader)); + // examine if the node belongs to us + bool nodeFound = false; + for (auto node_ip: pMSR->mClientNodes) { + if (node_ip == aph.addr) { + nodeFound = true; + break; + } + } + + if (!nodeFound) { + Logger::logLine("UNKNOWN remote node is talking to us, skipping this packet! (" + std::string(inet_ntoa({aph.addr})) + ")"); + continue; + } + + // check index continuity + if (lastIndex > 0 && (aph.index) != (lastIndex + 1)) { + Logger::logLine("NON-CONTINUOUS packet indices! (" + std::to_string(lastIndex) + " -> " + std::to_string(aph.index) + ")"); + // TODO Should skip here? + } + + lastIndex = aph.index; + // ----------- TIME PROCESSING --------------- + // fetch timestamp array + NodePacketTimestamps &ts = allNodeTS[aph.addr]; + // store timestamp ts[1] = ts[0]; // shift FIFO ts[0] = {(int64_t) aph.timestamp_s, (int64_t) aph.timestamp_ns}; // store new timestamp @@ -186,8 +221,10 @@ void MultiStreamReceiver::fnRecv(MultiStreamReceiver *pMSR) { Timestamp d = (ts[0] - ts[1]) / (double) pMSR->mAcqFmt.mch_samples_per_packet; // validity check on time step size - if (llabs(d.to_ns() - ((int64_t) (1E+09 / pMSR->mAcqFmt.sampling_rate_Hz))) > timeDiffVariation_ns) { - Logger::logLine("SKIPPED packet due to large packet period variation!"); + uint64_t diff = llabs(d.to_ns() - ((int64_t) (1E+09 / pMSR->mAcqFmt.sampling_rate_Hz))); + if (diff > timeDiffVariation_ns) { + Logger::logLine("SKIPPED packet due to large packet period variation! (" + std::to_string(diff) + " ns)"); + Logger::logLine("DEBUG " + std::to_string(aph.debug)); continue; // skip this packet if time step is outside the valid range } @@ -245,3 +282,7 @@ void MultiStreamReceiver::fnRecv(MultiStreamReceiver *pMSR) { } } } + +size_t MultiStreamReceiver::getChannelCount() const { + return mAcqFmt.channel_count * mClientNodes.size(); +} diff --git a/libwfr/src/MultiStreamReceiver.h b/libwfr/src/MultiStreamReceiver.h index 2cc7332..3b29139 100644 --- a/libwfr/src/MultiStreamReceiver.h +++ b/libwfr/src/MultiStreamReceiver.h @@ -25,8 +25,6 @@ private: std::shared_ptr mRecvThread; // thread for reception static void fnRecv(MultiStreamReceiver *pMSR); // routine function running in separate thread bool mRunning; - std::vector> mpSampleWriters; // sample writers for streams - //std::mutex mSampleInsertMtx; // mutex protecting against sample insertion collision when calling pSampleWriter->addSamples(...) std::shared_ptr mpMSP; // multistream processor INSTANCE (pointer to...) private: AcquisitionFormat mAcqFmt; // acquisition format @@ -43,6 +41,7 @@ public: void start(const std::shared_ptr &pMSP); // start reception void stop(); // stop reception + size_t getChannelCount() const; // get number of data channels virtual ~MultiStreamReceiver(); }; diff --git a/libwfr/src/MultiStreamToFile.cpp b/libwfr/src/MultiStreamToFile.cpp index a0b687f..6135998 100644 --- a/libwfr/src/MultiStreamToFile.cpp +++ b/libwfr/src/MultiStreamToFile.cpp @@ -31,7 +31,7 @@ void MultiStreamToFile::createSampleWriters() { for (unsigned int &mClientNode: mNodes) { for (size_t ch = 0; ch < mAcqFmt.channel_count; ch++) { // create sample writer - std::string datasetName = std::string("node_") + inet_ntoa({mClientNode}) + "ch" + std::to_string(acc_ch); + std::string datasetName = std::string("node_") + inet_ntoa({mClientNode}) + "_CH" + std::to_string(acc_ch); mpSampleWriters.emplace_back(std::make_shared(datasetName, mAcqFmt)); // create hint diff --git a/libwfr/src/SampleWriter.cpp b/libwfr/src/SampleWriter.cpp index 8e2366a..ee5f5a3 100644 --- a/libwfr/src/SampleWriter.cpp +++ b/libwfr/src/SampleWriter.cpp @@ -69,7 +69,7 @@ void SampleWriter::addSamples(const uint8_t *pSamples, const std::shared_ptr mSamplingPeriod_ns_UB || d.to_ns() < mSamplingPeriod_ns_LB) { @@ -86,7 +86,7 @@ void SampleWriter::addSamples(const uint8_t *pSamples, const std::shared_ptr ServerBeacon::getNodesOnNetwork() { return nodeAddrs; } -std::list ServerBeacon::getNodesOnNetwork_str() { - std::list nodeAddrs; +std::vector ServerBeacon::getNodesOnNetwork_str() { + std::vector nodeAddrs; for (auto nodeInfo: getNodesOnNetwork()) { nodeAddrs.emplace_back(inet_ntoa(nodeInfo)); diff --git a/libwfr/src/ServerBeacon.h b/libwfr/src/ServerBeacon.h index d76daf5..9a13871 100644 --- a/libwfr/src/ServerBeacon.h +++ b/libwfr/src/ServerBeacon.h @@ -67,7 +67,7 @@ public: void stopBeacon(); // stop the beacon void singleScan(); // initiate a single scan std::list getNodesOnNetwork(); // get nodes connected to the same network - std::list getNodesOnNetwork_str(); // get nodes connected to the same network (list of string) + std::vector getNodesOnNetwork_str(); // get nodes connected to the same network (list of string) unsigned short getNodeNettermPort(in_addr addr); // get network terminal port of node void setScanCallback(const std::shared_ptr>& scanCB); // set scan callback void execCmdOnNode(in_addr addr, const std::string& cmd); // execute command on a node through netterm AND DON'T expect a response diff --git a/libwfr/src/Trigger.cpp b/libwfr/src/Trigger.cpp index d93da7e..bf3d047 100644 --- a/libwfr/src/Trigger.cpp +++ b/libwfr/src/Trigger.cpp @@ -2,6 +2,7 @@ // Created by epagris on 2022.05.04.. // +#include #include "Trigger.h" TriggerSettings::TriggerSettings() { @@ -19,19 +20,19 @@ bool TriggerSettings::sample(double x) { // ------------------------ -SlopeTrigger::SlopeTrigger() { +EdgeTrigger::EdgeTrigger() { level = 0; - slope = RISING; - SlopeTrigger::reset(); + edge = RISING; + EdgeTrigger::reset(); } -void SlopeTrigger::reset() { +void EdgeTrigger::reset() { TriggerSettings::reset(); x_prev = 0; } -bool SlopeTrigger::sample(double x) { +bool EdgeTrigger::sample(double x) { // do not act on first sample if (mFirstSample) { mFirstSample = false; @@ -42,11 +43,13 @@ bool SlopeTrigger::sample(double x) { bool trig = false; // on every other sample... - if (((slope == RISING) && (x_prev < level && x > level)) || - ((slope == FALLING) && (x_prev > level && x < level))) { + if (((edge == RISING) && (x_prev < level && x > level)) || + ((edge == FALLING) && (x_prev > level && x < level))) { trig = true; } + //std::cout << x << " - " << level << std::endl; + x_prev = x; return trig; diff --git a/libwfr/src/Trigger.h b/libwfr/src/Trigger.h index cbe2edb..7e6ca90 100644 --- a/libwfr/src/Trigger.h +++ b/libwfr/src/Trigger.h @@ -6,6 +6,7 @@ #define WFR_APP_TRIGGER_H #include +#include "ICreatable.h" // Dummy trigger base struct TriggerSettings { @@ -20,17 +21,17 @@ public: virtual bool sample(double x); // insert sample into the trigger }; -// Simple slope trigger -struct SlopeTrigger : public TriggerSettings { +// Simple edge trigger +struct EdgeTrigger : public TriggerSettings, public ICreatable { public: - enum SlopeType { RISING, FALLING }; + enum EdgeType { RISING, FALLING }; private: double x_prev; // previous sample public: double level; // trigger level - SlopeType slope; // slope direction + EdgeType edge; // edge direction public: - SlopeTrigger(); // constr. + EdgeTrigger(); // constr. void reset() override; bool sample(double x) override; }; diff --git a/libwfr/src/audio_types.h b/libwfr/src/audio_types.h index a946bc9..3ed71a7 100644 --- a/libwfr/src/audio_types.h +++ b/libwfr/src/audio_types.h @@ -6,6 +6,7 @@ #define WFR_AUDIO_TYPES_H #include +#include #define PERIOD_LEN_DEF (STEREO_BUF_LEN / 2) @@ -18,9 +19,11 @@ typedef struct { uint32_t timestamp_s, timestamp_ns; // timestamp - uint32_t sample_cnt; // count of all-channel samples in packet + uint32_t index; + uint32_t debug; + /*uint32_t sample_cnt; // count of all-channel samples in packet uint16_t sample_size; // size of a single (mono) sample - uint16_t channel_count; // number of channels + uint16_t channel_count; // number of channels*/ in_addr_t addr; // client node address //AudioSampleType16_DEF pData[MCH_SAMP_PER_PCKT_DEF]; // buffer for stereo data } AudioPayloadHeader; diff --git a/libwfr/src/libwfr.h b/libwfr/src/libwfr.h new file mode 100644 index 0000000..182d63a --- /dev/null +++ b/libwfr/src/libwfr.h @@ -0,0 +1,22 @@ +// +// Created by epagris on 2022.05.05.. +// + +#ifndef WFR_APP_LIBWFR_H +#define WFR_APP_LIBWFR_H + +#include "AcquisitionFormat.h" +#include "audio_types.h" +#include "Callback.h" +#include "ChannelBuffer.h" +#include "ICreatable.h" +#include "Logger.h" +#include "MultiStreamOscilloscope.h" +#include "MultiStreamProcessor.h" +#include "MultiStreamReceiver.h" +#include "MultiStreamToFile.h" +#include "SampleWriter.h" +#include "ServerBeacon.h" +#include "Trigger.h" + +#endif //WFR_APP_LIBWFR_H diff --git a/utils/Semaphore.cpp b/libwfr/src/utils/Semaphore.cpp similarity index 100% rename from utils/Semaphore.cpp rename to libwfr/src/utils/Semaphore.cpp diff --git a/utils/Semaphore.h b/libwfr/src/utils/Semaphore.h similarity index 100% rename from utils/Semaphore.h rename to libwfr/src/utils/Semaphore.h diff --git a/main.cpp b/main.cpp index 4a09bad..8c20967 100644 --- a/main.cpp +++ b/main.cpp @@ -15,6 +15,8 @@ #include "libwfr/src/ServerBeacon.h" #include "src/GUI.h" +#include "libwfr/src/libwfr.h" + //uint8_t pRecvBuf[8096] __attribute__ ((aligned (32))); std::shared_ptr globs; @@ -30,7 +32,7 @@ int main(int argc, char * argv[]) { Logger::startLogging(); - auto a = MultiStreamToFile::create("asd"); + // ----------------- @@ -44,6 +46,27 @@ int main(int argc, char * argv[]) { // create beacon auto beacon = globs->beacon = std::make_shared(); beacon->setInterfaceAddr(mcastIFAddr); + beacon->singleScan(); + auto nodes = beacon->getNodesOnNetwork_str(); + AcquisitionFormat fmt(beacon->queryFromNode(nodes.front(), "snd acqformat mr")); + MultiStreamReceiver msr(nodes, fmt); + auto osc = MultiStreamOscilloscope::create(); + + auto et = EdgeTrigger::create(); + et->level = 0.5; + et->edge = EdgeTrigger::RISING; + osc->trigger = et; + + msr.start(osc); + beacon->execCmdOnAllNodes("snd connect 10.42.0.1 20220"); + + osc->armTrigger(); + auto samples = osc->capture(); + + beacon->execCmdOnAllNodes("snd disconnect"); + msr.stop(); + + return 0; // init and start GUI Glib::init(); diff --git a/python/src/wfsmodule.cpp b/python/src/wfsmodule.cpp index a51620a..d853ce0 100644 --- a/python/src/wfsmodule.cpp +++ b/python/src/wfsmodule.cpp @@ -1,10 +1,7 @@ #include #include -#include -#include "AcquisitionFormat.h" -#include "MultiStreamToFile.h" -#include "MultiStreamReceiver.h" +#include #define STRINGIFY(x) #x #define MACRO_STRINGIFY(x) STRINGIFY(x) @@ -66,7 +63,8 @@ PYBIND11_MODULE(pywfs, m) { .def(py::init, const AcquisitionFormat &, unsigned short>(), "nodes_str"_a, "acqFormat"_a, "port"_a = MultiStreamReceiver::DEFAULT_PORT) .def(py::init, const std::string &, unsigned short>(), "nodes_str"_a, "acqFormat_str"_a, "port"_a = MultiStreamReceiver::DEFAULT_PORT) .def("listen", &MultiStreamReceiver::start) - .def("close", &MultiStreamReceiver::stop); + .def("close", &MultiStreamReceiver::stop) + .def("getChannelCount", &MultiStreamReceiver::getChannelCount); // MultiStreamProcessor (abstract class) py::class_> MSP(m, "MultiStreamProcessor"); @@ -75,6 +73,47 @@ PYBIND11_MODULE(pywfs, m) { py::class_, MultiStreamProcessor>(m, "MultiStreamToFile") .def_static("create", &MultiStreamToFile::create); + // MultiStreamOscilloscope + py::class_, MultiStreamProcessor>(m, "MultiStreamOscilloscope") + .def(py::init<>()) + .def_static("create", &MultiStreamOscilloscope::create) + .def_readwrite("trigger", &MultiStreamOscilloscope::trigger) + .def_readwrite("verticalScale", &MultiStreamOscilloscope::verticalScale) + .def("armTrigger", &MultiStreamOscilloscope::armTrigger) + .def("triggerNow", &MultiStreamOscilloscope::triggerNow) + .def("capture", &MultiStreamOscilloscope::capture) + .def("getChannelCount", &MultiStreamOscilloscope::getChannelCount) + .def("setScreenPeriod", &MultiStreamOscilloscope::setScreenPeriod) + .def("getScreenPeriod", &MultiStreamOscilloscope::getScreenPeriod) + .def("setTriggerPosition", &MultiStreamOscilloscope::setTriggerPosition) + .def("getTriggerPosition", &MultiStreamOscilloscope::getTriggerPosition) + .def("getScreenTimeLimits", &MultiStreamOscilloscope::getScreenTimeLimits) + ; + + // SamplePoint + py::class_(m, "SamplePoint") + .def(py::init<>()) + .def_readwrite("t", &MultiStreamOscilloscope::SamplePoint::t) + .def_readwrite("x", &MultiStreamOscilloscope::SamplePoint::x); + + // TriggerSettings + py::class_>(m, "TriggerSettings") + .def(py::init<>()) + .def_readwrite("ch", &TriggerSettings::ch); + + // EdgeTrigger + py::class_, TriggerSettings>(m, "EdgeTrigger") + .def(py::init<>()) + .def("create", &EdgeTrigger::create) + .def_readwrite("level", &EdgeTrigger::level) + .def_readwrite("edge", &EdgeTrigger::edge); + + // EdgeType + py::enum_(m, "EdgeType") + .value("RISING", EdgeTrigger::EdgeType::RISING) + .value("FALLING", EdgeTrigger::EdgeType::FALLING) + .export_values(); + // // m.def("subtract", [](int i, int j) { return i - j; }, R"pbdoc( // Subtract two numbers diff --git a/src/ALSADevice.h b/src/ALSADevice.h index 007287c..674e95c 100644 --- a/src/ALSADevice.h +++ b/src/ALSADevice.h @@ -9,7 +9,7 @@ #include #include #include "MemoryPool.h" -#include "../utils/Semaphore.h" +#include "../libwfr/src/utils/Semaphore.h" class ALSADevice { private: