diff --git a/DeviceAdapters/NIDAQ/NIDAQ.h b/DeviceAdapters/NIDAQ/NIDAQ.h index 73c4f876c..013f69d8a 100644 --- a/DeviceAdapters/NIDAQ/NIDAQ.h +++ b/DeviceAdapters/NIDAQ/NIDAQ.h @@ -422,7 +422,54 @@ class DigitalOutputPort : public CStateDeviceBase, virtual void GetName(char* name) const; virtual bool Busy() { return false; } - virtual unsigned long GetNumberOfPositions()const { return numPos_; } // replace with numPos_ once API allows not creating Labels + // For 8-bit ports, all states are enumerable. For wider ports, only + // enumerate positions that have been explicitly labeled (e.g. via config + // file). This prevents MMCore from iterating billions of states. + // The State property limits still allow the full value range. + static const long maxEnumerablePositions_ = 65536; + virtual unsigned long GetNumberOfPositions()const { + if (numPos_ <= 255) + return numPos_ + 1; + return (highestLabeledPos_ >= 0 && highestLabeledPos_ < maxEnumerablePositions_) + ? (unsigned long)(highestLabeledPos_ + 1) : 0; + } + + // Return the numeric string as default when no explicit label exists. + // This prevents errors when the Label property is read for unlabeled states. + virtual int GetPositionLabel(long pos, char* label) const + { + int ret = CStateDeviceBase::GetPositionLabel(pos, label); + if (ret != DEVICE_OK) + { + CDeviceUtils::CopyLimitedString(label, std::to_string(pos).c_str()); + return DEVICE_OK; + } + return ret; + } + + // Override to fill in default labels for gaps when labels are added + // beyond the auto-generated range (e.g. from config file on wide ports). + virtual int SetPositionLabel(long pos, const char* label) + { + // Fill in default labels for any positions below pos that have no label + if (pos > highestLabeledPos_ && pos < maxEnumerablePositions_) + { + char buf[MM::MaxStrLength]; + for (long i = highestLabeledPos_ + 1; i < pos; i++) + { + // Only fill if no label exists yet + if (CStateDeviceBase::GetPositionLabel(i, buf) != DEVICE_OK) + { + CStateDeviceBase::SetPositionLabel(i, std::to_string(i).c_str()); + } + } + highestLabeledPos_ = pos; + } + return CStateDeviceBase::SetPositionLabel(pos, label); + } + + // Gate (shutter) interface + int SetGateOpen(bool open); // action interface // ---------------- @@ -451,9 +498,9 @@ class DigitalOutputPort : public CStateDeviceBase, bool sequenceRunning_; bool blanking_; bool blankOnLow_; - bool open_; long pos_; long numPos_; + long highestLabeledPos_; uInt32 portWidth_; long inputLine_; long firstStateSlider_; diff --git a/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp b/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp index 575628568..f1857a362 100644 --- a/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp +++ b/DeviceAdapters/NIDAQ/NIDigitalOutputPort.cpp @@ -32,9 +32,9 @@ DigitalOutputPort::DigitalOutputPort(const std::string& port) : sequenceRunning_(false), blanking_(false), blankOnLow_(true), - open_(true), pos_(0), numPos_(0), + highestLabeledPos_(-1), portWidth_(0), nrOfStateSliders_(4), inputLine_(8), @@ -142,19 +142,32 @@ int DigitalOutputPort::Initialize() } else { - numPos_ = (1 << portWidth_) - 1; + numPos_ = (portWidth_ >= 32) ? 0x7FFFFFFF : (1L << portWidth_) - 1; } pAct = new CPropertyAction(this, &DigitalOutputPort::OnState); CreateIntegerProperty("State", 0, false, pAct); SetPropertyLimits("State", 0, numPos_); + pAct = new CPropertyAction(this, &CStateBase::OnLabel); + CreateProperty(MM::g_Keyword_Label, "", MM::String, false, pAct); + + // For 8-bit ports, pre-populate labels for all states. + // For wider ports, labels are only created when explicitly set + // (e.g. via config file). The SetPositionLabel override fills gaps. + if (numPos_ <= 255) + { + for (long i = 0; i <= numPos_; i++) + { + SetPositionLabel(i, std::to_string(i).c_str()); + } + } + // In case someone left some pins high: SetState(0); // Gate Closed Position CreateProperty(MM::g_Keyword_Closed_Position, "0", MM::Integer, false); - GetGateOpen(open_); if (supportsBlankingAndSequencing_ && (uint32_t) nrOfStateSliders_ >= portWidth_) { nrOfStateSliders_ = portWidth_ - 1; @@ -211,6 +224,23 @@ void DigitalOutputPort::GetName(char* name) const } +int DigitalOutputPort::SetGateOpen(bool open) +{ + // During blanking/sequencing, hardware controls the outputs via + // the trigger input. We cannot delegate to the base class because + // it calls SetPosition -> OnState, which rejects changes during + // sequencing. Just record the gate state for when we return to + // software-timed mode. + if (sequenceRunning_) + return DEVICE_OK; + + // Delegate to base class, which updates gateOpen_ and calls + // SetPosition -> OnState. OnState handles the blanking vs + // software-timed distinction. + return CStateDeviceBase::SetGateOpen(open); +} + + int DigitalOutputPort::OnState(MM::PropertyBase* pProp, MM::ActionType eAct) { if (eAct == MM::BeforeGet) @@ -222,16 +252,25 @@ int DigitalOutputPort::OnState(MM::PropertyBase* pProp, MM::ActionType eAct) if (sequenceRunning_) return ERR_SEQUENCE_RUNNING; - bool gateOpen; - GetGateOpen(gateOpen); long pos; pProp->Get(pos); - if ((pos == pos_) && (open_ == gateOpen)) - return DEVICE_OK; + bool gateOpen; + GetGateOpen(gateOpen); - long closed_state; - GetProperty(MM::g_Keyword_Closed_Position, closed_state); - long newState = gateOpen ? pos : closed_state; + // When blanking is active, hardware controls on/off via the trigger + // input, so always use the requested state. Gate only applies in + // software-timed mode (blanking off). + long newState; + if (blanking_) + { + newState = pos; + } + else + { + long closed_state; + GetProperty(MM::g_Keyword_Closed_Position, closed_state); + newState = gateOpen ? pos : closed_state; + } // pause blanking, otherwise most cards will error int err; @@ -248,7 +287,6 @@ int DigitalOutputPort::OnState(MM::PropertyBase* pProp, MM::ActionType eAct) if (err == DEVICE_OK) { pos_ = pos; - open_ = gateOpen; } else { diff --git a/DeviceAdapters/PVCAM/PVCAMUniversal.cpp b/DeviceAdapters/PVCAM/PVCAMUniversal.cpp index 1f9a9de2d..aed364425 100644 --- a/DeviceAdapters/PVCAM/PVCAMUniversal.cpp +++ b/DeviceAdapters/PVCAM/PVCAMUniversal.cpp @@ -5716,11 +5716,15 @@ int Universal::abortAcquisitionInternal() { pollingThd_->setStop(true); pollingThd_->wait(); + // AcqFinished() is already called by PollingThreadExiting() } } else { acqThd_->Pause(); + // Notify the core that acquisition has finished so that + // AutoShutter can close the shutter. + GetCoreCallback()->AcqFinished(this, nRet); } customDiskWriter_->Stop();