diff --git a/DeviceAdapters/DemoCamera/DemoCamera.cpp b/DeviceAdapters/DemoCamera/DemoCamera.cpp index cde5082f7..827793408 100644 --- a/DeviceAdapters/DemoCamera/DemoCamera.cpp +++ b/DeviceAdapters/DemoCamera/DemoCamera.cpp @@ -308,6 +308,16 @@ int CDemoCamera::Initialize() else LogMessage(NoHubError); + // Example of how to create standard properties + // CPropertyAction *pActsp = new CPropertyAction (this, &CDemoCamera::OnTestStandardProperty); + // int nRett = CreateTestStandardProperty("123", pActsp); + // assert(nRett == DEVICE_OK); + + // CPropertyAction *pActsp2 = new CPropertyAction (this, &CDemoCamera::OnTestWithValuesStandardProperty); + // int nRettt = CreateTestWithValuesStandardProperty("value1", pActsp2); + // assert(nRettt == DEVICE_OK); + + // set property list // ----------------- @@ -1384,6 +1394,39 @@ int CDemoCamera::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +int CDemoCamera::OnTestStandardProperty(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set("test"); + return DEVICE_OK; + } + else if (eAct == MM::AfterSet) + { + std::string val; + pProp->Get(val); + return DEVICE_OK; + } + return DEVICE_OK; +} + +int CDemoCamera::OnTestWithValuesStandardProperty(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set("value1"); + return DEVICE_OK; + } + else if (eAct == MM::AfterSet) + { + std::string val; + pProp->Get(val); + return DEVICE_OK; + } + return DEVICE_OK; +} + + /** * Handles "PixelType" property. */ diff --git a/DeviceAdapters/DemoCamera/DemoCamera.h b/DeviceAdapters/DemoCamera/DemoCamera.h index 1de99da9b..5e7a115ee 100644 --- a/DeviceAdapters/DemoCamera/DemoCamera.h +++ b/DeviceAdapters/DemoCamera/DemoCamera.h @@ -175,6 +175,8 @@ class CDemoCamera : public CCameraBase // ---------------- int OnMaxExposure(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTestProperty(MM::PropertyBase* pProp, MM::ActionType eAct, long); + int OnTestStandardProperty(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTestWithValuesStandardProperty(MM::PropertyBase* pProp, MM::ActionType eAct); int OnAsyncFollower(MM::PropertyBase* pProp, MM::ActionType eAct); int OnAsyncLeader(MM::PropertyBase* pProp, MM::ActionType eAct); void SlowPropUpdate(std::string leaderValue); diff --git a/MMCore/Devices/DeviceInstance.cpp b/MMCore/Devices/DeviceInstance.cpp index a36ea75b7..0c2602297 100644 --- a/MMCore/Devices/DeviceInstance.cpp +++ b/MMCore/Devices/DeviceInstance.cpp @@ -376,6 +376,16 @@ DeviceInstance::Initialize() ThrowError("Device already initialized (or initialization already attempted)"); initializeCalled_ = true; ThrowIfError(pImpl_->Initialize()); + + // Check for that all required standard properties implemented + char failedProperty[MM::MaxStrLength]; + if (!pImpl_->ImplementsOrSkipsStandardProperties(failedProperty)) { + // shutdown the device + Shutdown(); + ThrowError("Device " + GetLabel() + + " does not implement required standard property: " + + std::string(failedProperty)); + } initialized_ = true; } diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 84cef7177..9d452cf35 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -38,6 +38,8 @@ #include #include #include +#include +#include // common error messages const char* const g_Msg_ERR = "Unknown error in the device"; @@ -117,6 +119,27 @@ class CDeviceBase : public T CDeviceUtils::CopyLimitedString(name, moduleName_.c_str()); } + //// Standard properties are created using only these dedicated functions + // Such functions should all be defined here, and which device types they apply + // + // to is handled in MMDevice.h using the MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE macro + // int CreateTestStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { + // return CreateStandardProperty(value, pAct); + // } + + // int CreateTestWithValuesStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { + // // just make the values the required ones here. Also option to add + // // additional ones in real situations + // return CreateStandardProperty(value, pAct, + // MM::g_TestWithValuesStandardProperty.requiredValues); + // } + + // Every standard property must either be created or explicitly skipped using + // a method like this + // void SkipTestStandardProperty() { + // SkipStandardProperty(); + // } + /** * Assigns description string for a device (for use only by the calling code). */ @@ -534,6 +557,14 @@ class CDeviceBase : public T return false; } + virtual bool HasStandardProperty(const char* name) const + { + // prepend standard property prefix to name + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += name; + return HasProperty(fullName.c_str()); + } + /** * Returns the number of allowed property values. * If the set of property values is not defined, not bounded, @@ -569,6 +600,40 @@ class CDeviceBase : public T return true; } + bool ImplementsOrSkipsStandardProperties(char* failedProperty) const { + // Get the device type + MM::DeviceType deviceType = this->GetType(); + + // Look up properties for this device type + auto it = MM::internal::GetDeviceTypeStandardPropertiesMap().find(deviceType); + if (it != MM::internal::GetDeviceTypeStandardPropertiesMap().end()) { + // Iterate through all properties for this device type + const auto& properties = it->second; + for (const auto& prop : properties) { + // Construct the full property name with prefix + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += prop.name; + + // Skip checking if this property is in the skipped list + if (skippedStandardProperties_.find(fullName) != skippedStandardProperties_.end()) { + continue; + } + + // Check if the device has implemented it + if (!HasProperty(fullName.c_str())) { + // If not, copy in the name of the property and return false + CDeviceUtils::CopyLimitedString(failedProperty, fullName.c_str()); + return false; + } + } + } + + // All required properties are implemented or explicitly skipped + return true; + } + + + /** * Creates a new property for the device. * @param name - property name @@ -596,6 +661,7 @@ class CDeviceBase : public T */ int CreatePropertyWithHandler(const char* name, const char* value, MM::PropertyType eType, bool readOnly, int(U::*memberFunction)(MM::PropertyBase* pProp, MM::ActionType eAct), bool isPreInitProperty=false) { + // Check for reserved delimiter (handled in CreateProperty) CPropertyAction* pAct = new CPropertyAction((U*) this, memberFunction); return CreateProperty(name, value, eType, readOnly, pAct, isPreInitProperty); } @@ -1219,6 +1285,148 @@ class CDeviceBase : public T } private: + + /** + * Low-level implementation for creating standard properties. + * + * This template method uses SFINAE (Substitution Failure Is Not An Error) to ensure + * that standard properties can only be created for device types they're valid for. + * The IsStandardPropertyValid template specializations determine which properties + * are valid for which device types at compile time. + * + * Note: This is a private implementation method. Device implementations should use + * the specific convenience methods like CreateStandardBinningProperty() instead. + * + * @param PropPtr - Pointer to the standard property definition + * @param value - Initial value for the property + * @param pAct - Optional action functor to handle property changes + * @return DEVICE_OK if successful, error code otherwise + */ + template + typename std::enable_if::value, int>::type + CreateStandardProperty(const char* value, MM::ActionFunctor* pAct = 0, const std::vector& values = {}) { + + // Create the full property name with prefix + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += PropRef.name; + + // Create the property with all appropriate fields + int ret = properties_.CreateProperty(fullName.c_str(), value, PropRef.type, + PropRef.isReadOnly, pAct, PropRef.isPreInit, true); + if (ret != DEVICE_OK) + return ret; + + // Set limits if they exist + if (PropRef.hasLimits()) { + ret = SetPropertyLimits(fullName.c_str(), PropRef.lowerLimit, PropRef.upperLimit); + if (ret != DEVICE_OK) + return ret; + } + + // Ensure the initial value is allowed if the property has predefined allowed values + if (!PropRef.allowedValues.empty()) { + if (std::find(PropRef.allowedValues.begin(), PropRef.allowedValues.end(), value) == PropRef.allowedValues.end()) { + return DEVICE_INVALID_PROPERTY_VALUE; + } + } + + // Set the allowed values using the existing SetStandardPropertyValues function + if (!values.empty() || !PropRef.requiredValues.empty()) { + ret = SetStandardPropertyValues(values); + if (ret != DEVICE_OK) + return ret; + } + + // Remove from skipped properties if it was previously marked as skipped + skippedStandardProperties_.erase(fullName); + + return DEVICE_OK; + } + + /** + * Sets allowed values for a standard property, clearing any existing values first. + * Performs the same validation as when creating the property. + * + * @param PropRef - Reference to the standard property definition + * @param values - Vector of values to set as allowed values + * @return DEVICE_OK if successful, error code otherwise + */ + template + typename std::enable_if::value, int>::type + SetStandardPropertyValues(const std::vector& values) { + // Create the full property name with prefix + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += PropRef.name; + + // Check if the property exists + if (!HasProperty(fullName.c_str())) { + return DEVICE_INVALID_PROPERTY; + } + + // Ensure all supplied values are allowed if the property has predefined allowed values + if (!PropRef.allowedValues.empty()) { + for (const std::string& val : values) { + if (std::find(PropRef.allowedValues.begin(), PropRef.allowedValues.end(), val) == PropRef.allowedValues.end()) { + return DEVICE_INVALID_PROPERTY_VALUE; + } + } + } + + // Check if all required values are present + if (!PropRef.requiredValues.empty()) { + for (const std::string& val : PropRef.requiredValues) { + if (std::find(values.begin(), values.end(), val) == values.end()) { + return DEVICE_INVALID_PROPERTY_VALUE; + } + } + } + + // Clear existing values + int ret = properties_.ClearAllowedValues(fullName.c_str(), true); + if (ret != DEVICE_OK) + return ret; + + // Add the new values + for (const std::string& val : values) { + ret = properties_.AddAllowedValue(fullName.c_str(), val.c_str(), true); + if (ret != DEVICE_OK) + return ret; + } + + return DEVICE_OK; + } + + // This one is purely for providing better error messages at compile time + // When an function for setting an invalid standard property is called, + // this function will be called and will cause a compilation error. + template + typename std::enable_if::value, int>::type + CreateStandardProperty(const char* /*value*/, MM::ActionFunctor* /*pAct*/ = 0, + const std::vector& /*values*/ = std::vector()) { + static_assert(MM::internal::IsStandardPropertyValid::value, + "This standard property is not valid for this device type. Check the MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE definitions in MMDevice.h"); + return DEVICE_UNSUPPORTED_COMMAND; // This line will never execute due to the static_assert + } + + // Helper method to mark a required standard property as skipped + template + void SkipStandardProperty() { + // Only allow skipping properties that are valid for this device type + if (MM::internal::IsStandardPropertyValid::value) { + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += PropRef.name; + skippedStandardProperties_.insert(fullName); + // Check if the property already exists. If so, delete it. + // This is needed because standard properties may be created dynamically not during + // initialization. For example, if they depend on the value of another property, + // and this is not known other than by setting that value on the device. In this case, + // the standard property will be created and destroyed as the values change. + if (HasProperty(fullName.c_str())) { + properties_.Delete(fullName.c_str()); + } + } + } + bool PropertyDefined(const char* propName) const { return properties_.Find(propName) != 0; @@ -1261,6 +1469,10 @@ class CDeviceBase : public T // specific information about the errant property, etc. mutable std::string morePropertyErrorInfo_; std::string parentID_; + + // Set to track which standard properties are explicitly skipped + std::set skippedStandardProperties_; + }; // Forbid instantiation of CDeviceBase diff --git a/MMDevice/MMDevice-SharedRuntime.vcxproj b/MMDevice/MMDevice-SharedRuntime.vcxproj index 8df751203..a0ff43c76 100644 --- a/MMDevice/MMDevice-SharedRuntime.vcxproj +++ b/MMDevice/MMDevice-SharedRuntime.vcxproj @@ -14,7 +14,6 @@ - @@ -88,4 +87,4 @@ - + \ No newline at end of file diff --git a/MMDevice/MMDevice-SharedRuntime.vcxproj.filters b/MMDevice/MMDevice-SharedRuntime.vcxproj.filters index 2f38adf00..80e136161 100644 --- a/MMDevice/MMDevice-SharedRuntime.vcxproj.filters +++ b/MMDevice/MMDevice-SharedRuntime.vcxproj.filters @@ -20,9 +20,6 @@ Source Files - - Source Files - Source Files @@ -62,4 +59,4 @@ Header Files - + \ No newline at end of file diff --git a/MMDevice/MMDevice-StaticRuntime.vcxproj b/MMDevice/MMDevice-StaticRuntime.vcxproj index b1317341a..0770c8ce7 100644 --- a/MMDevice/MMDevice-StaticRuntime.vcxproj +++ b/MMDevice/MMDevice-StaticRuntime.vcxproj @@ -14,7 +14,6 @@ - @@ -90,4 +89,4 @@ - + \ No newline at end of file diff --git a/MMDevice/MMDevice-StaticRuntime.vcxproj.filters b/MMDevice/MMDevice-StaticRuntime.vcxproj.filters index 2f38adf00..80e136161 100644 --- a/MMDevice/MMDevice-StaticRuntime.vcxproj.filters +++ b/MMDevice/MMDevice-StaticRuntime.vcxproj.filters @@ -20,9 +20,6 @@ Source Files - - Source Files - Source Files @@ -62,4 +59,4 @@ Header Files - + \ No newline at end of file diff --git a/MMDevice/MMDevice.cpp b/MMDevice/MMDevice.cpp deleted file mode 100644 index 36c82ef97..000000000 --- a/MMDevice/MMDevice.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// AUTHOR: Mark Tsuchida, May 2014 -// -// COPYRIGHT: University of California, San Francisco, 2014 -// -// LICENSE: This file is distributed under the BSD license. -// License text is included with the source distribution. -// -// This file is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty -// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. - -#include "MMDevice.h" - -namespace MM { - -// Definitions for static const data members. -// -// Note: Do not try to move these initializers to the header. The C++ standard -// allows initializing a static const enum data member inline (inside the class -// definition, where the member is _declared_), but still requires a -// _definition_ (in which case, the definition should not have an initializer). -// However, Microsoft VC++ has a nonstandard extension that allows you to leave -// out the definition altogether, if an initializer is supplied at the -// declaration. Because of that nonstandard behavior, VC++ issues a warning -// (LNK4006) if the initializer is supplied with the declaration _and_ a -// definition is (correctly) provided. So, to compile correctly with a -// standards-conformant compiler _and_ avoid warnings from VC++, we need to -// leave the initializers out of the declarations, and supply them here with -// the definitions. See: -// http://connect.microsoft.com/VisualStudio/feedback/details/802091/lnk4006-reported-for-static-const-members-that-is-initialized-in-the-class-definition - -const DeviceType Generic::Type = GenericDevice; -const DeviceType Camera::Type = CameraDevice; -const DeviceType Shutter::Type = ShutterDevice; -const DeviceType Stage::Type = StageDevice; -const DeviceType XYStage::Type = XYStageDevice; -const DeviceType State::Type = StateDevice; -const DeviceType Serial::Type = SerialDevice; -const DeviceType AutoFocus::Type = AutoFocusDevice; -const DeviceType ImageProcessor::Type = ImageProcessorDevice; -const DeviceType SignalIO::Type = SignalIODevice; -const DeviceType Magnifier::Type = MagnifierDevice; -const DeviceType SLM::Type = SLMDevice; -const DeviceType Galvo::Type = GalvoDevice; -const DeviceType Hub::Type = HubDevice; - -} // namespace MM diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index c674ac0b7..05dde50d0 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -43,14 +43,17 @@ #include "DeviceUtils.h" #include "ImageMetadata.h" #include "DeviceThreads.h" +#include "Property.h" #include #include #include +#include #include #include #include #include +#include // To be removed once the deprecated Get/SetModuleHandle() is removed: #ifdef _WIN32 @@ -205,6 +208,104 @@ namespace MM { }; + //////////////////////////// + ///// Standard properties + //////////////////////////// + + namespace internal { + //// Helper functions/macros for compile time and runtime checking of standard properties + + // Map from device types to standard properties + inline std::unordered_map>& GetDeviceTypeStandardPropertiesMap() { + static std::unordered_map> devicePropertiesMap; + return devicePropertiesMap; + } + + // Register a standard property with its valid device types + inline void RegisterStandardProperty(const StandardProperty& prop, std::initializer_list deviceTypes) { + for (MM::DeviceType deviceType : deviceTypes) { + GetDeviceTypeStandardPropertiesMap()[deviceType].push_back(prop); + } + } + + // Check if a property is valid for a specific device type + inline bool IsPropertyValidForDeviceType(const StandardProperty& prop, MM::DeviceType deviceType) { + auto it = GetDeviceTypeStandardPropertiesMap().find(deviceType); + if (it == GetDeviceTypeStandardPropertiesMap().end()) { + return false; + } + + const auto& validProperties = it->second; + return std::find(validProperties.begin(), validProperties.end(), prop) != validProperties.end(); + } + + // The below code is a way to enable compile time checking of which + // standard properties are are valid for which device types. This enables + // methods for setting these standard properties defined once + // in CDeviceBase and then conditionally enabled in child classes based on + // the MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE macro below + // In addition to making this link, a dedicated method for the standard property + // should be created in CDeviceBase. + template + struct IsStandardPropertyValid { + static const bool value = false; // Default to false + }; + + // The top struct enables compile time checking of standard property + // creation methods (ie that you can call the CreateXXXXStandardProperty + // in a device type that doesn't support it) + // The bottom part adds the standard property to a runtime registry + // which enables higher level code the query the standard properties + // associated with a particular device type and check that everything + // is correct (ie all required properties are implemented after + // device initialization) + #define MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(DeviceType, PropertyRef) \ + template <> \ + struct MM::internal::IsStandardPropertyValid { \ + static const bool value = true; \ + }; \ + namespace { \ + static const bool PropertyRef##_registered = (MM::internal::RegisterStandardProperty(PropertyRef, {DeviceType}), true); \ + } + } // namespace internal + + /////// Standard property definitions + // Each standard property is defined here, and then linked to one or more + // device types using the MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE macro. + // This allows compile time checking that the property is valid for a + // particular device type, and also allows runtime querying of which + // properties are supported by a given device. + + // Specific standard properties + // static const MM::StandardProperty g_TestStandardProperty{ + // "Test", // name + // String, // type + // false, // isReadOnly + // false, // isPreInit + // {}, // allowedValues (empty vector) + // {}, // requiredValues (empty vector) + // PropertyLimitUndefined, // lowerLimit + // PropertyLimitUndefined, // upperLimit + // }; + + // MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestStandardProperty) + + + // static const std::vector testRequiredValues = {"value1", "value2", "value3"}; + // static const MM::StandardProperty g_TestWithValuesStandardProperty{ + // "TestWithValues", // name + // String, // type + // false, // isReadOnly + // false, // isPreInit + // {}, // allowedValues (empty vector) + // testRequiredValues, // requiredValues + // PropertyLimitUndefined, // lowerLimit + // PropertyLimitUndefined, // upperLimit + // }; + + // MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestWithValuesStandardProperty) + + /** * Generic device interface. */ @@ -226,6 +327,7 @@ namespace MM { virtual int GetPropertyType(const char* name, MM::PropertyType& pt) const = 0; virtual unsigned GetNumberOfPropertyValues(const char* propertyName) const = 0; virtual bool GetPropertyValueAt(const char* propertyName, unsigned index, char* value) const = 0; + virtual bool ImplementsOrSkipsStandardProperties(char* failedProperty) const = 0; /** * Sequences can be used for fast acquisitions, synchronized by TTLs rather than * computer commands. @@ -309,7 +411,7 @@ namespace MM { { public: virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = GenericDevice; }; /** @@ -321,7 +423,7 @@ namespace MM { virtual ~Camera() {} virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = CameraDevice; // Camera API /** @@ -557,7 +659,7 @@ namespace MM { // Device API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = ShutterDevice; // Shutter API virtual int SetOpen(bool open = true) = 0; @@ -580,7 +682,7 @@ namespace MM { // Device API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = StageDevice; // Stage API virtual int SetPositionUm(double pos) = 0; @@ -680,7 +782,7 @@ namespace MM { // Device API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = XYStageDevice; // XYStage API // it is recommended that device adapters implement the "Steps" methods @@ -766,7 +868,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = StateDevice; // MMStateDevice API virtual int SetPosition(long pos) = 0; @@ -792,7 +894,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = SerialDevice; // Serial API virtual PortType GetPortType() const = 0; @@ -814,7 +916,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = AutoFocusDevice; // AutoFocus API virtual int SetContinuousFocusing(bool state) = 0; @@ -840,7 +942,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = ImageProcessorDevice; // image processor API virtual int Process(unsigned char* buffer, unsigned width, unsigned height, unsigned byteDepth) = 0; @@ -859,7 +961,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = SignalIODevice; // signal io API virtual int SetGateOpen(bool open = true) = 0; @@ -943,7 +1045,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = MagnifierDevice; virtual double GetMagnification() = 0; }; @@ -963,7 +1065,7 @@ namespace MM { virtual ~SLM() {} virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = SLMDevice; // SLM API /** @@ -1122,7 +1224,7 @@ namespace MM { virtual ~Galvo() {} virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = GalvoDevice; //Galvo API: @@ -1249,7 +1351,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = HubDevice; /** * Instantiate all available child peripheral devices. diff --git a/MMDevice/MMDeviceConstants.h b/MMDevice/MMDeviceConstants.h index 65b0a5c90..149d72705 100644 --- a/MMDevice/MMDeviceConstants.h +++ b/MMDevice/MMDeviceConstants.h @@ -88,6 +88,7 @@ #define DEVICE_SEQUENCE_TOO_LARGE 39 #define DEVICE_OUT_OF_MEMORY 40 #define DEVICE_NOT_YET_IMPLEMENTED 41 +#define DEVICE_MISSING_REQUIRED_PROPERTY 42 namespace MM { diff --git a/MMDevice/Property.cpp b/MMDevice/Property.cpp index 7088a8f81..32e8e72dd 100644 --- a/MMDevice/Property.cpp +++ b/MMDevice/Property.cpp @@ -326,12 +326,21 @@ unsigned MM::PropertyCollection::GetSize() const return (unsigned) properties_.size(); } -int MM::PropertyCollection::CreateProperty(const char* pszName, const char* pszValue, MM::PropertyType eType, bool bReadOnly, MM::ActionFunctor* pAct, bool isPreInitProperty) +int MM::PropertyCollection::CreateProperty(const char* pszName, const char* pszValue, MM::PropertyType eType, + bool bReadOnly, MM::ActionFunctor* pAct, bool isPreInitProperty, bool standard) { // check if the name already exists if (Find(pszName)) return DEVICE_DUPLICATE_PROPERTY; + if (!standard) + { + // make sure it doesn't begin with the reserved prefix for standard properties + std::string prefixAndDelim = std::string(g_KeywordStandardPropertyPrefix); + if (std::string(pszName).find(prefixAndDelim) == 0) + return DEVICE_INVALID_PROPERTY; + } + MM::Property* pProp=0; switch(eType) @@ -363,12 +372,20 @@ int MM::PropertyCollection::CreateProperty(const char* pszName, const char* pszV return DEVICE_OK; } -int MM::PropertyCollection::SetAllowedValues(const char* pszName, std::vector& values) +int MM::PropertyCollection::SetAllowedValues(const char* pszName, std::vector& values, bool standard) { MM::Property* pProp = Find(pszName); if (!pProp) return DEVICE_INVALID_PROPERTY; // name not found + if (!standard) + { + // make sure it doesn't begin with the reserved prefix for standard properties + std::string prefixAndDelim = std::string(g_KeywordStandardPropertyPrefix); + if (std::string(pszName).find(prefixAndDelim) == 0) + return DEVICE_INVALID_PROPERTY; + } + pProp->ClearAllowedValues(); for (unsigned i=0; iAddAllowedValue(values[i].c_str()); @@ -376,32 +393,56 @@ int MM::PropertyCollection::SetAllowedValues(const char* pszName, std::vectorClearAllowedValues(); return DEVICE_OK; } -int MM::PropertyCollection::AddAllowedValue(const char* pszName, const char* value, long data) +int MM::PropertyCollection::AddAllowedValue(const char* pszName, const char* value, long data, bool standard) { MM::Property* pProp = Find(pszName); if (!pProp) return DEVICE_INVALID_PROPERTY; // name not found + if (!standard) + { + // make sure it doesn't begin with the reserved prefix for standard properties + std::string prefixAndDelim = std::string(g_KeywordStandardPropertyPrefix); + if (std::string(pszName).find(prefixAndDelim) == 0) + return DEVICE_INVALID_PROPERTY; + } + pProp->AddAllowedValue(value, data); return DEVICE_OK; } -int MM::PropertyCollection::AddAllowedValue(const char* pszName, const char* value) +int MM::PropertyCollection::AddAllowedValue(const char* pszName, const char* value, bool standard) { MM::Property* pProp = Find(pszName); if (!pProp) return DEVICE_INVALID_PROPERTY; // name not found + if (!standard) + { + // make sure it doesn't begin with the reserved prefix for standard properties + std::string prefixAndDelim = std::string(g_KeywordStandardPropertyPrefix); + if (std::string(pszName).find(prefixAndDelim) == 0) + return DEVICE_INVALID_PROPERTY; + } + pProp->AddAllowedValue(value); return DEVICE_OK; } @@ -499,3 +540,17 @@ int MM::PropertyCollection::Apply(const char* pszName) return pProp->Apply(); } + +int MM::PropertyCollection::Delete(const char* pszName) +{ + MM::Property* pProp = Find(pszName); + if (!pProp) + return DEVICE_INVALID_PROPERTY; + + // remove it from the map + properties_.erase(pszName); + + delete pProp; + + return DEVICE_OK; +} diff --git a/MMDevice/Property.h b/MMDevice/Property.h index 24c1a8920..1a3fa5f35 100644 --- a/MMDevice/Property.h +++ b/MMDevice/Property.h @@ -27,9 +27,46 @@ #include #include #include +#include namespace MM { +// Standard Properties +const char* const g_KeywordStandardPropertyPrefix = "api//"; + +// Define NaN for use in property definitions +const double PropertyLimitUndefined = std::numeric_limits::quiet_NaN(); + +// Standard property metadata structure +struct StandardProperty { + // Helper to check if limits are defined + bool hasLimits() const { + return !std::isnan(lowerLimit) && !std::isnan(upperLimit); + } + + // Equality operator for comparison in containers + bool operator==(const StandardProperty& other) const { + return name == other.name && + type == other.type && + isReadOnly == other.isReadOnly && + isPreInit == other.isPreInit && + allowedValues == other.allowedValues && + requiredValues == other.requiredValues && + lowerLimit == other.lowerLimit && + upperLimit == other.upperLimit; + } + + std::string name; // Full property name (without prefix) + PropertyType type; // Float, String, or Integer + bool isReadOnly; // Whether property is read-only + bool isPreInit; // Whether property should be set before initialization + std::vector allowedValues; // (for String properties) if empty, no restrictions + std::vector requiredValues; // (for String properties) if empty, no restrictions + double lowerLimit = PropertyLimitUndefined; // Lower limit for numeric properties (NaN if not limited) + double upperLimit = PropertyLimitUndefined; // Upper limit for numeric properties (NaN if not limited) +}; + + /** * Base API for all device properties. * This interface is used by action functors. @@ -437,12 +474,12 @@ class PropertyCollection PropertyCollection(); ~PropertyCollection(); - int CreateProperty(const char* name, const char* value, PropertyType eType, bool bReadOnly, ActionFunctor* pAct=0, bool isPreInitProperty=false); + int CreateProperty(const char* name, const char* value, PropertyType eType, bool bReadOnly, ActionFunctor* pAct=0, bool isPreInitProperty=false, bool standard=false); int RegisterAction(const char* name, ActionFunctor* fpAct); - int SetAllowedValues(const char* name, std::vector& values); - int ClearAllowedValues(const char* name); - int AddAllowedValue(const char* name, const char* value, long data); - int AddAllowedValue(const char* name, const char* value); + int SetAllowedValues(const char* name, std::vector& values, bool standard=false); + int ClearAllowedValues(const char* name, bool standard=false); + int AddAllowedValue(const char* name, const char* value, long data, bool standard=false); + int AddAllowedValue(const char* name, const char* value, bool standard=false); int GetPropertyData(const char* name, const char* value, long& data); int GetCurrentPropertyData(const char* name, long& data); int Set(const char* propName, const char* Value); @@ -455,11 +492,11 @@ class PropertyCollection int ApplyAll(); int Update(const char* Name); int Apply(const char* Name); + int Delete(const char* pszName); private: typedef std::map CPropArray; CPropArray properties_; }; - } // namespace MM